├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── Changelog.txt ├── Constellation ├── CMakeLists.txt ├── Constellation.cpp ├── ConstellationDisplay.cpp ├── ConstellationDisplay.hpp └── ConstellationWork.cpp ├── LICENSE_1_0.txt ├── LogicAnalyzer ├── CMakeLists.txt ├── LogicAnalyzer.cpp ├── LogicAnalyzerDisplay.cpp └── LogicAnalyzerDisplay.hpp ├── Periodogram ├── CMakeLists.txt ├── Periodogram.cpp ├── PeriodogramChannel.cpp ├── PeriodogramChannel.hpp ├── PeriodogramDisplay.cpp ├── PeriodogramDisplay.hpp └── PeriodogramWork.cpp ├── PlotterUtils ├── PlotUtilsConfig.hpp ├── PothosPlotPicker.cpp ├── PothosPlotPicker.hpp ├── PothosPlotStyler.hpp ├── PothosPlotUtils.cpp ├── PothosPlotUtils.hpp ├── PothosPlotter.cpp ├── PothosPlotter.hpp └── PothosPlotterFFTUtils.hpp ├── QwtWidgets ├── CMakeLists.txt ├── QwtDial.cpp ├── QwtKnob.cpp ├── QwtSlider.cpp └── QwtThermo.cpp ├── README.md ├── Spectrogram ├── CMakeLists.txt ├── ColorMapEntry.cpp ├── GeneratedColorMaps.cpp ├── GeneratedColorMaps.hpp ├── QwtColorMapMaker.cpp ├── Spectrogram.cpp ├── SpectrogramDisplay.cpp ├── SpectrogramDisplay.hpp ├── SpectrogramRaster.hpp ├── SpectrogramWork.cpp └── gen_colormaps.py ├── WaveMonitor ├── CMakeLists.txt ├── WaveMonitor.cpp ├── WaveMonitorDisplay.cpp ├── WaveMonitorDisplay.hpp └── WaveMonitorWork.cpp ├── cmake └── FindQwt.cmake └── debian ├── changelog ├── compat ├── control ├── copyright ├── docs ├── libpothosplotterutils0.2.0.install ├── pothos-plotters.install ├── pothos0.7-modules-plotters.install ├── rules └── source └── format /.gitignore: -------------------------------------------------------------------------------- 1 | build/** 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "qwt6"] 2 | path = qwt6 3 | url = https://github.com/opencor/qwt.git 4 | branch = master 5 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ######################################################################## 2 | # Project setup 3 | ######################################################################## 4 | cmake_minimum_required(VERSION 3.1.0) 5 | project(PothosPlotters CXX) 6 | 7 | if(${CMAKE_PROJECT_NAME} STREQUAL ${PROJECT_NAME}) 8 | find_package(Pothos "0.6.0" CONFIG REQUIRED) 9 | else() 10 | find_package(Pothos CONFIG REQUIRED) #in-tree build 11 | endif() 12 | 13 | list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) 14 | 15 | #args to set the CMAKE_PREFIX_PATH environment variable 16 | if (CMAKE_PREFIX_PATH) 17 | set(ENV{CMAKE_PREFIX_PATH} ${CMAKE_PREFIX_PATH}) 18 | endif (CMAKE_PREFIX_PATH) 19 | 20 | include(GNUInstallDirs) 21 | 22 | ######################################################################## 23 | # Qt devel setup 24 | ######################################################################## 25 | find_package(QT NAMES Qt6 Qt5 COMPONENTS Core) 26 | find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets Concurrent) 27 | message(STATUS "QT_VERSION_MAJOR=${QT_VERSION_MAJOR}") 28 | set(CMAKE_AUTOMOC ON) 29 | 30 | ######################################################################## 31 | # Spuce library 32 | ######################################################################## 33 | if(NOT SPUCE_IN_TREE) 34 | find_package(Spuce CONFIG) 35 | endif(NOT SPUCE_IN_TREE) 36 | 37 | if (Spuce_FOUND) 38 | message(STATUS "Spuce_VERSION: ${Spuce_VERSION}") 39 | message(STATUS "Spuce_INCLUDE_DIRS: ${Spuce_INCLUDE_DIRS}") 40 | message(STATUS "Spuce_LIBRARIES: ${Spuce_LIBRARIES}") 41 | if (NOT TARGET spuce) 42 | add_library(spuce INTERFACE) 43 | target_link_libraries(spuce INTERFACE "${Spuce_LIBRARIES}") 44 | target_include_directories(spuce INTERFACE "${Spuce_INCLUDE_DIRS}") 45 | endif() 46 | else (Spuce_FOUND) 47 | message(WARNING "Spuce filter designer library not found...") 48 | endif (Spuce_FOUND) 49 | 50 | ######################################################################## 51 | ## Feature registration 52 | ######################################################################## 53 | include(FeatureSummary) 54 | include(CMakeDependentOption) 55 | cmake_dependent_option(ENABLE_PLOTTERS "Enable Pothos Plotters component" ON "Pothos_FOUND;Qt${QT_VERSION_MAJOR}_FOUND" OFF) 56 | add_feature_info(Plotters ENABLE_PLOTTERS "Graphical plotter widgets for the GUI") 57 | 58 | ######################################################################## 59 | # Qwt setup 60 | ######################################################################## 61 | find_package(Qwt) 62 | 63 | if (QWT_FOUND AND NOT QWT_VERSION_STRING) 64 | message(WARNING "Failed to extract QWT_VERSION_STRING from ${QWT_INCLUDE_DIRS} - using built-in Qwt") 65 | set(QWT_FOUND FALSE) 66 | endif() 67 | 68 | if (QWT_FOUND) 69 | message(STATUS "QWT_VERSION_STRING: ${QWT_VERSION_STRING}") 70 | if (QWT_VERSION_STRING LESS "6.1") 71 | set(QWT_FOUND FALSE) 72 | message(STATUS "Qwt older than v6.1 - using built-in Qwt") 73 | endif() 74 | else (QWT_FOUND) 75 | message(STATUS "Qwt not found - using built-in Qwt") 76 | endif (QWT_FOUND) 77 | 78 | if (QWT_FOUND) 79 | 80 | add_library(PothosQwt INTERFACE) 81 | target_link_libraries(PothosQwt INTERFACE "${QWT_LIBRARIES}") 82 | target_include_directories(PothosQwt INTERFACE "${QWT_INCLUDE_DIRS}") 83 | target_compile_definitions(PothosQwt INTERFACE -DQWT_DLL) 84 | message(STATUS "QWT_INCLUDE_DIRS: ${QWT_INCLUDE_DIRS}") 85 | message(STATUS "QWT_LIBRARIES: ${QWT_LIBRARIES}") 86 | 87 | elseif (ENABLE_PLOTTERS) 88 | 89 | #compiling qwt from source requires many more Qt libraries 90 | find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets Concurrent OpenGL Svg PrintSupport) 91 | 92 | set(QWT_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/qwt6/src) 93 | 94 | if (NOT EXISTS ${QWT_SRC_DIR}) 95 | message(FATAL_ERROR "Qwt submodule not configured (git submodule update --init --recursive)") 96 | endif () 97 | 98 | set (QWT_VERSION_STRING 0) 99 | set ( _VERSION_FILE ${QWT_SRC_DIR}/qwt_global.h ) 100 | if ( EXISTS ${_VERSION_FILE} ) 101 | file ( STRINGS ${_VERSION_FILE} _VERSION_LINE REGEX "define[ ]+QWT_VERSION_STR" ) 102 | if ( _VERSION_LINE ) 103 | string ( REGEX REPLACE ".*define[ ]+QWT_VERSION_STR[ ]+\"(.*)\".*" "\\1" QWT_VERSION_STRING "${_VERSION_LINE}" ) 104 | endif () 105 | endif () 106 | message(STATUS "QWT_VERSION_STRING: ${QWT_VERSION_STRING}") 107 | 108 | file(GLOB QWT_SOURCES "${QWT_SRC_DIR}/*.cpp") 109 | add_library(PothosQwt SHARED ${QWT_SOURCES}) 110 | target_include_directories(PothosQwt PUBLIC ${QWT_SRC_DIR}) 111 | set_target_properties(PothosQwt PROPERTIES DEFINE_SYMBOL "QWT_MAKEDLL") 112 | set_target_properties(PothosQwt PROPERTIES VERSION ${QWT_VERSION_STRING}) 113 | target_link_libraries(PothosQwt PRIVATE Qt${QT_VERSION_MAJOR}::Widgets) 114 | target_link_libraries(PothosQwt PRIVATE Qt${QT_VERSION_MAJOR}::OpenGL) 115 | target_link_libraries(PothosQwt PRIVATE Qt${QT_VERSION_MAJOR}::Concurrent) 116 | target_link_libraries(PothosQwt PRIVATE Qt${QT_VERSION_MAJOR}::PrintSupport) 117 | target_link_libraries(PothosQwt PRIVATE Qt${QT_VERSION_MAJOR}::Svg) 118 | target_compile_definitions(PothosQwt PUBLIC -DQWT_DLL) 119 | install(TARGETS PothosQwt 120 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} # .so file 121 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} # .lib file 122 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} # .dll file 123 | ) 124 | 125 | endif (QWT_FOUND) 126 | 127 | ######################################################################## 128 | # plotter utils library 129 | ######################################################################## 130 | if (ENABLE_PLOTTERS) 131 | 132 | file(GLOB PLOTTER_UTILS_SOURCES "PlotterUtils/*.cpp") 133 | add_library(PothosPlotterUtils SHARED ${PLOTTER_UTILS_SOURCES}) 134 | target_include_directories(PothosPlotterUtils PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/PlotterUtils) 135 | set_target_properties(PothosPlotterUtils PROPERTIES DEFINE_SYMBOL "POTHOS_PLOTTER_UTILS_MAKEDLL") 136 | set_target_properties(PothosPlotterUtils PROPERTIES VERSION 0.2.0) 137 | target_link_libraries(PothosPlotterUtils PRIVATE Qt${QT_VERSION_MAJOR}::Widgets) 138 | target_link_libraries(PothosPlotterUtils PRIVATE PothosQwt) 139 | target_link_libraries(PothosPlotterUtils PRIVATE Pothos) 140 | install(TARGETS PothosPlotterUtils 141 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} # .so file 142 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} # .lib file 143 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} # .dll file 144 | ) 145 | 146 | endif (ENABLE_PLOTTERS) 147 | 148 | ######################################################################## 149 | # Build plotter widgets 150 | ######################################################################## 151 | add_subdirectory(Constellation) 152 | add_subdirectory(LogicAnalyzer) 153 | add_subdirectory(Periodogram) 154 | add_subdirectory(QwtWidgets) 155 | add_subdirectory(Spectrogram) 156 | add_subdirectory(WaveMonitor) 157 | -------------------------------------------------------------------------------- /Changelog.txt: -------------------------------------------------------------------------------- 1 | This this the changelog file for the Pothos Plotters toolkit. 2 | 3 | Release 0.4.2 (2021-07-25) 4 | ========================== 5 | 6 | - updated signal/slots connection style 7 | - updated qwt submodule to 6.1.6 8 | - Update build to support Qt6 and newer cmake target style 9 | - Support dark themes by allowing plot background default color 10 | 11 | Release 0.4.1 (2018-04-24) 12 | ========================== 13 | 14 | - Add relativeFrequencySelected to periodogram and spectrogram 15 | 16 | Release 0.4.0 (2017-12-25) 17 | ========================== 18 | 19 | - Replaced use of callVoid() and callProxy() with 0.6 call API 20 | - Renamed the project repo from pothos-plotters to PothosPlotters 21 | 22 | Release 0.3.1 (2017-06-22) 23 | ========================== 24 | 25 | - Added slot for enableXAxis/enableYAxis for thread safety 26 | - Added clearChannels() slot to wave monitor and periodogram 27 | - Hide wave monitor labels when corresponding curve is hidden 28 | 29 | Release 0.3.0 (2017-04-27) 30 | ========================== 31 | 32 | - Spectrogram edit widget requires PothosGUI 0.5.0 33 | 34 | Release 0.2.2 (2017-04-23) 35 | ========================== 36 | 37 | - Consistently use QString format for curve labels 38 | - Support power spectrum packets in Periodogram 39 | - Restore enabled state in thermo setAlarmLevel 40 | 41 | Release 0.2.1 (2016-09-24) 42 | ========================== 43 | 44 | - FindQwt.cmake defaults to the qt5 version first 45 | - Tweaks to QwtPlotCurve::CurveStyle for WaveMonitor 46 | - Added curve style and color to Constellation plotter 47 | - Fix typo in name of ColorMapEntry registration 48 | - Added missing libpoco-dev to debian Build-Depends 49 | 50 | Release 0.2.0 (2016-08-02) 51 | ========================== 52 | 53 | - Re-factored plotter utils library and changed to 0.2.0 54 | - Added color map selection to the Spectrogram plotter 55 | - Updated Qwt6 submodule to qwt-6.1.3 and renamed directory 56 | - Added QwtThermo, QwtKnob, and QwtDial widget to QwtWidgets 57 | - Added saveState()/restoreState() hooks for all plotters 58 | - Replace use of QwtText in metatype due to Qt unload bug 59 | 60 | Release 0.1.1 (2016-05-10) 61 | ========================== 62 | 63 | - Clear wave monitor channels on data type change 64 | - Added FFT mode option to the Spectrogram plotter 65 | - Added FFT mode option to the Periodogram plotter 66 | - Added full-scale option to the Spectrogram plotter 67 | - Added full-scale option to the Periodogram plotter 68 | 69 | Release 0.1.0 (2015-12-16) 70 | ========================== 71 | 72 | - Added debian packaging support for plotters toolkit 73 | - Support start label ID in spectrogram and periodogram 74 | - Use Spuce window designer in the FFT power spectrum 75 | - Handle empty version string from FindQwt result 76 | - Created Qwt widgets module for widgets that require Qwt 77 | - Moved plotters and qwt widgets from pothos-widgets toolkit 78 | -------------------------------------------------------------------------------- /Constellation/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ######################################################################## 2 | ## Feature registration 3 | ######################################################################## 4 | cmake_dependent_option(ENABLE_PLOTTERS_CONSTELLATION "Enable Pothos Plotters.Constellation component" ON "ENABLE_PLOTTERS" OFF) 5 | add_feature_info(" Constellation" ENABLE_PLOTTERS_CONSTELLATION "Constellation complex IQ data plotter") 6 | if (NOT ENABLE_PLOTTERS_CONSTELLATION) 7 | return() 8 | endif() 9 | 10 | ######################################################################## 11 | # Build constellation plot module 12 | ######################################################################## 13 | POTHOS_MODULE_UTIL( 14 | TARGET Constellation 15 | SOURCES 16 | Constellation.cpp 17 | ConstellationWork.cpp 18 | ConstellationDisplay.cpp 19 | DOC_SOURCES Constellation.cpp 20 | LIBRARIES 21 | Qt${QT_VERSION_MAJOR}::Widgets 22 | PothosQwt 23 | PothosPlotterUtils 24 | DESTINATION plotters 25 | ) 26 | -------------------------------------------------------------------------------- /Constellation/Constellation.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2016 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include "ConstellationDisplay.hpp" 5 | #include 6 | #include 7 | 8 | /*********************************************************************** 9 | * |PothosDoc Constellation 10 | * 11 | * The periodogram plot displays a live constellation plot of a complex signal. 12 | * 13 | * |category /Plotters 14 | * |keywords plot constellation scatter 15 | * |alias /widgets/constellation 16 | * 17 | * |param title The title of the plot 18 | * |default "Constellation" 19 | * |widget StringEntry() 20 | * |preview valid 21 | * 22 | * |param displayRate[Display Rate] How often the plotter updates. 23 | * |default 10.0 24 | * |units updates/sec 25 | * |preview disable 26 | * 27 | * |param numPoints[Num Points] The number of points per plot capture. 28 | * |default 1024 29 | * |preview disable 30 | * 31 | * |param autoScale[Auto-Scale] Enable automatic scaling for the axes. 32 | * |default false 33 | * |option [Auto scale] true 34 | * |option [Use limits] false 35 | * |preview disable 36 | * |tab Axis 37 | * 38 | * |param xRange[X-Axis Range] The minimum and maximum values for the X-Axis. 39 | * When auto scale is off, this parameter controls the horizontal axis. 40 | * |default [-1.5, 1.5] 41 | * |preview disable 42 | * |tab Axis 43 | * 44 | * |param yRange[Y-Axis Range] The minimum and maximum values for the Y-Axis. 45 | * When auto scale is off, this parameter controls the vertical axis. 46 | * |default [-1.5, 1.5] 47 | * |preview disable 48 | * |tab Axis 49 | * 50 | * |param enableXAxis[Enable X-Axis] Show or hide the horizontal axis markers. 51 | * |option [Show] true 52 | * |option [Hide] false 53 | * |default true 54 | * |preview disable 55 | * |tab Axis 56 | * 57 | * |param enableYAxis[Enable Y-Axis] Show or hide the vertical axis markers. 58 | * |option [Show] true 59 | * |option [Hide] false 60 | * |default true 61 | * |preview disable 62 | * |tab Axis 63 | * 64 | * |param curveStyle[Curve Style] The curve style for the plotter. 65 | * |default "DOTS" 66 | * |option [Line] "LINE" 67 | * |option [Dash] "DASH" 68 | * |option [Dots] "DOTS" 69 | * |preview disable 70 | * |tab Curve 71 | * 72 | * |param curveColor[Curve Color] The color for the plotter curve. 73 | * |widget ColorPicker() 74 | * |default "blue" 75 | * |tab Curve 76 | * |preview disable 77 | * 78 | * |mode graphWidget 79 | * |factory /plotters/constellation(remoteEnv) 80 | * |setter setTitle(title) 81 | * |setter setDisplayRate(displayRate) 82 | * |setter setNumPoints(numPoints) 83 | * |setter setAutoScale(autoScale) 84 | * |setter setXRange(xRange) 85 | * |setter setYRange(yRange) 86 | * |setter enableXAxis(enableXAxis) 87 | * |setter enableYAxis(enableYAxis) 88 | * |setter setCurveStyle(curveStyle) 89 | * |setter setCurveColor(curveColor) 90 | **********************************************************************/ 91 | class Constellation : public Pothos::Topology 92 | { 93 | public: 94 | static Topology *make(const Pothos::ProxyEnvironment::Sptr &remoteEnv) 95 | { 96 | return new Constellation(remoteEnv); 97 | } 98 | 99 | Constellation(const Pothos::ProxyEnvironment::Sptr &remoteEnv) 100 | { 101 | _display.reset(new ConstellationDisplay()); 102 | _display->setName("Display"); 103 | 104 | auto registry = remoteEnv->findProxy("Pothos/BlockRegistry"); 105 | _trigger = registry.call("/comms/wave_trigger"); 106 | _trigger.call("setName", "Trigger"); 107 | _trigger.call("setMode", "PERIODIC"); 108 | 109 | //register calls in this topology 110 | this->registerCall(this, POTHOS_FCN_TUPLE(Constellation, setDisplayRate)); 111 | this->registerCall(this, POTHOS_FCN_TUPLE(Constellation, setNumPoints)); 112 | 113 | //connect to internal display block 114 | this->connect(this, "setTitle", _display, "setTitle"); 115 | this->connect(this, "setAutoScale", _display, "setAutoScale"); 116 | this->connect(this, "setXRange", _display, "setXRange"); 117 | this->connect(this, "setYRange", _display, "setYRange"); 118 | this->connect(this, "enableXAxis", _display, "enableXAxis"); 119 | this->connect(this, "enableYAxis", _display, "enableYAxis"); 120 | this->connect(this, "setCurveStyle", _display, "setCurveStyle"); 121 | this->connect(this, "setCurveColor", _display, "setCurveColor"); 122 | 123 | //connect to the internal snooper block 124 | this->connect(this, "setDisplayRate", _trigger, "setEventRate"); 125 | this->connect(this, "setNumPoints", _trigger, "setNumPoints"); 126 | 127 | //connect stream ports 128 | this->connect(this, 0, _trigger, 0); 129 | this->connect(_trigger, 0, _display, 0); 130 | } 131 | 132 | Pothos::Object opaqueCallMethod(const std::string &name, const Pothos::Object *inputArgs, const size_t numArgs) const 133 | { 134 | //calls that go to the topology 135 | try 136 | { 137 | return Pothos::Topology::opaqueCallMethod(name, inputArgs, numArgs); 138 | } 139 | catch (const Pothos::BlockCallNotFound &){} 140 | 141 | //forward everything else to display 142 | return _display->opaqueCallMethod(name, inputArgs, numArgs); 143 | } 144 | 145 | void setDisplayRate(const double rate) 146 | { 147 | _trigger.call("setEventRate", rate); 148 | } 149 | 150 | void setNumPoints(const size_t num) 151 | { 152 | _trigger.call("setNumPoints", num); 153 | } 154 | 155 | private: 156 | Pothos::Proxy _trigger; 157 | std::shared_ptr _display; 158 | }; 159 | 160 | /*********************************************************************** 161 | * registration 162 | **********************************************************************/ 163 | static Pothos::BlockRegistry registerConstellation( 164 | "/plotters/constellation", &Constellation::make); 165 | 166 | static Pothos::BlockRegistry registerConstellationOldPath( 167 | "/widgets/constellation", &Constellation::make); 168 | -------------------------------------------------------------------------------- /Constellation/ConstellationDisplay.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2021 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include "ConstellationDisplay.hpp" 5 | #include "PothosPlotter.hpp" 6 | #include "PothosPlotUtils.hpp" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | ConstellationDisplay::ConstellationDisplay(void): 15 | _mainPlot(new PothosPlotter(this, POTHOS_PLOTTER_GRID | POTHOS_PLOTTER_ZOOM)), 16 | _autoScale(false), 17 | _queueDepth(0), 18 | _curveStyle("DOTS"), 19 | _curveColor("blue") 20 | { 21 | //setup block 22 | this->registerCall(this, POTHOS_FCN_TUPLE(ConstellationDisplay, widget)); 23 | this->registerCall(this, POTHOS_FCN_TUPLE(ConstellationDisplay, setTitle)); 24 | this->registerCall(this, POTHOS_FCN_TUPLE(ConstellationDisplay, setAutoScale)); 25 | this->registerCall(this, POTHOS_FCN_TUPLE(ConstellationDisplay, title)); 26 | this->registerCall(this, POTHOS_FCN_TUPLE(ConstellationDisplay, autoScale)); 27 | this->registerCall(this, POTHOS_FCN_TUPLE(ConstellationDisplay, setXRange)); 28 | this->registerCall(this, POTHOS_FCN_TUPLE(ConstellationDisplay, setYRange)); 29 | this->registerCall(this, POTHOS_FCN_TUPLE(ConstellationDisplay, enableXAxis)); 30 | this->registerCall(this, POTHOS_FCN_TUPLE(ConstellationDisplay, enableYAxis)); 31 | this->registerCall(this, POTHOS_FCN_TUPLE(ConstellationDisplay, setCurveStyle)); 32 | this->registerCall(this, POTHOS_FCN_TUPLE(ConstellationDisplay, setCurveColor)); 33 | this->setupInput(0); 34 | 35 | //layout 36 | auto layout = new QHBoxLayout(this); 37 | layout->setSpacing(0); 38 | layout->setContentsMargins(QMargins()); 39 | layout->addWidget(_mainPlot); 40 | 41 | //setup plotter 42 | { 43 | connect(_mainPlot->zoomer(), &QwtPlotZoomer::zoomed, this, &ConstellationDisplay::handleZoomed); 44 | } 45 | 46 | //register types passed to gui thread from work 47 | qRegisterMetaType("Pothos::BufferChunk"); 48 | } 49 | 50 | ConstellationDisplay::~ConstellationDisplay(void) 51 | { 52 | return; 53 | } 54 | 55 | void ConstellationDisplay::setTitle(const QString &title) 56 | { 57 | QMetaObject::invokeMethod(_mainPlot, "setTitle", Qt::QueuedConnection, Q_ARG(QString, title)); 58 | } 59 | 60 | void ConstellationDisplay::setAutoScale(const bool autoScale) 61 | { 62 | _autoScale = autoScale; 63 | QMetaObject::invokeMethod(this, "handleUpdateAxis", Qt::QueuedConnection); 64 | } 65 | 66 | void ConstellationDisplay::setXRange(const std::vector &range) 67 | { 68 | if (range.size() != 2) throw Pothos::RangeException("ConstellationDisplay::setXRange()", "range vector must be size 2"); 69 | _xRange = range; 70 | QMetaObject::invokeMethod(this, "handleUpdateAxis", Qt::QueuedConnection); 71 | } 72 | 73 | void ConstellationDisplay::setYRange(const std::vector &range) 74 | { 75 | if (range.size() != 2) throw Pothos::RangeException("ConstellationDisplay::setYRange()", "range vector must be size 2"); 76 | _yRange = range; 77 | QMetaObject::invokeMethod(this, "handleUpdateAxis", Qt::QueuedConnection); 78 | } 79 | 80 | QVariant ConstellationDisplay::saveState(void) const 81 | { 82 | return _mainPlot->state(); 83 | } 84 | 85 | void ConstellationDisplay::restoreState(const QVariant &state) 86 | { 87 | _mainPlot->setState(state); 88 | } 89 | 90 | void ConstellationDisplay::handleUpdateAxis(void) 91 | { 92 | if (_curve) 93 | { 94 | QwtPlotCurve::CurveStyle style(QwtPlotCurve::Dots); 95 | Qt::PenStyle penStyle(Qt::SolidLine); 96 | qreal width = 1.0; 97 | if (_curveStyle == "LINE") {style = QwtPlotCurve::Lines; penStyle = Qt::SolidLine; width = 1.0;} 98 | if (_curveStyle == "DASH") {style = QwtPlotCurve::Lines; penStyle = Qt::DashLine; width = 1.0;} 99 | if (_curveStyle == "DOTS") {style = QwtPlotCurve::Dots; penStyle = Qt::DotLine; width = 2.0;} 100 | _curve->setPen(pastelize(_curveColor), width, penStyle); 101 | _curve->setStyle(style); 102 | } 103 | 104 | if (_xRange.size() == 2) _mainPlot->setAxisScale(QwtPlot::xBottom, _xRange[0], _xRange[1]); 105 | if (_yRange.size() == 2) _mainPlot->setAxisScale(QwtPlot::yLeft, _yRange[0], _yRange[1]); 106 | 107 | _mainPlot->setAxisTitle(QwtPlot::xBottom, "In-Phase"); 108 | _mainPlot->setAxisTitle(QwtPlot::yLeft, "Quadrature"); 109 | 110 | _mainPlot->updateAxes(); //update after axis changes 111 | _mainPlot->zoomer()->setZoomBase(); //record current axis settings 112 | this->handleZoomed(_mainPlot->zoomer()->zoomBase()); //reload 113 | } 114 | 115 | void ConstellationDisplay::handleZoomed(const QRectF &rect) 116 | { 117 | //when zoomed all the way out, return to autoscale 118 | if (rect == _mainPlot->zoomer()->zoomBase() and _autoScale) 119 | { 120 | _mainPlot->setAxisAutoScale(QwtPlot::xBottom); 121 | _mainPlot->setAxisAutoScale(QwtPlot::yLeft); 122 | } 123 | } 124 | 125 | QString ConstellationDisplay::title(void) const 126 | { 127 | return _mainPlot->title().text(); 128 | } 129 | 130 | void ConstellationDisplay::enableXAxis(const bool enb) 131 | { 132 | QMetaObject::invokeMethod(_mainPlot, "enableAxis", Qt::QueuedConnection, Q_ARG(int, QwtPlot::xBottom), Q_ARG(bool, enb)); 133 | } 134 | 135 | void ConstellationDisplay::enableYAxis(const bool enb) 136 | { 137 | QMetaObject::invokeMethod(_mainPlot, "enableAxis", Qt::QueuedConnection, Q_ARG(int, QwtPlot::yLeft), Q_ARG(bool, enb)); 138 | } 139 | 140 | void ConstellationDisplay::setCurveStyle(const std::string &style) 141 | { 142 | _curveStyle = style; 143 | QMetaObject::invokeMethod(this, "handleUpdateAxis", Qt::QueuedConnection); 144 | } 145 | 146 | void ConstellationDisplay::setCurveColor(const QString &color) 147 | { 148 | _curveColor = color; 149 | QMetaObject::invokeMethod(this, "handleUpdateAxis", Qt::QueuedConnection); 150 | } 151 | -------------------------------------------------------------------------------- /Constellation/ConstellationDisplay.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2016 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #pragma once 5 | #include //_USE_MATH_DEFINES 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | class PothosPlotter; 16 | class QwtPlotCurve; 17 | 18 | class ConstellationDisplay : public QWidget, public Pothos::Block 19 | { 20 | Q_OBJECT 21 | public: 22 | 23 | ConstellationDisplay(void); 24 | 25 | ~ConstellationDisplay(void); 26 | 27 | QWidget *widget(void) 28 | { 29 | return this; 30 | } 31 | 32 | //! set the plotter's title 33 | void setTitle(const QString &title); 34 | 35 | void setAutoScale(const bool autoScale); 36 | 37 | void setXRange(const std::vector &range); 38 | 39 | void setYRange(const std::vector &range); 40 | 41 | QString title(void) const; 42 | 43 | bool autoScale(void) const 44 | { 45 | return _autoScale; 46 | } 47 | 48 | void enableXAxis(const bool enb); 49 | void enableYAxis(const bool enb); 50 | 51 | void setCurveStyle(const std::string &style); 52 | void setCurveColor(const QString &color); 53 | 54 | void work(void); 55 | 56 | //allow for standard resize controls with the default size policy 57 | QSize minimumSizeHint(void) const 58 | { 59 | return QSize(200, 200); 60 | } 61 | QSize sizeHint(void) const 62 | { 63 | return this->minimumSizeHint(); 64 | } 65 | 66 | public slots: 67 | 68 | QVariant saveState(void) const; 69 | 70 | void restoreState(const QVariant &value); 71 | 72 | private slots: 73 | void handleUpdateAxis(void); 74 | void handleSamples(const Pothos::BufferChunk &buff); 75 | void handleZoomed(const QRectF &rect); 76 | 77 | private: 78 | PothosPlotter *_mainPlot; 79 | bool _autoScale; 80 | std::vector _xRange; 81 | std::vector _yRange; 82 | std::unique_ptr _curve; 83 | std::atomic _queueDepth; 84 | std::string _curveStyle; 85 | QString _curveColor; 86 | }; 87 | -------------------------------------------------------------------------------- /Constellation/ConstellationWork.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2016 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include "ConstellationDisplay.hpp" 5 | #include "PothosPlotter.hpp" 6 | #include "PothosPlotUtils.hpp" 7 | #include 8 | #include 9 | #include 10 | 11 | /*********************************************************************** 12 | * work functions 13 | **********************************************************************/ 14 | void ConstellationDisplay::handleSamples(const Pothos::BufferChunk &buff) 15 | { 16 | if (_queueDepth.fetch_sub(1) != 1) return; 17 | 18 | //create curve that it doesnt exist 19 | if (not _curve) 20 | { 21 | _curve.reset(new QwtPlotCurve()); 22 | _curve->attach(_mainPlot); 23 | this->handleUpdateAxis(); 24 | } 25 | 26 | //convert to points and post to curve 27 | const auto samps = buff.as *>(); 28 | QVector points(buff.elements()); 29 | for (int i = 0; i < points.size(); i++) 30 | { 31 | points[i] = QPointF(samps[i].real(), samps[i].imag()); 32 | } 33 | _curve->setSamples(points); 34 | 35 | //replot 36 | _mainPlot->replot(); 37 | } 38 | 39 | void ConstellationDisplay::work(void) 40 | { 41 | auto inPort = this->input(0); 42 | 43 | if (not inPort->hasMessage()) return; 44 | const auto msg = inPort->popMessage(); 45 | 46 | //packet-based messages have payloads to plot 47 | if (msg.type() == typeid(Pothos::Packet)) 48 | { 49 | _queueDepth++; 50 | const auto &buff = msg.convert().payload; 51 | auto floatBuff = buff.convert(Pothos::DType(typeid(std::complex)), buff.elements()); 52 | QMetaObject::invokeMethod(this, "handleSamples", Qt::QueuedConnection, Q_ARG(Pothos::BufferChunk, floatBuff)); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /LICENSE_1_0.txt: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /LogicAnalyzer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ######################################################################## 2 | ## Feature registration 3 | ######################################################################## 4 | cmake_dependent_option(ENABLE_PLOTTERS_LOGICANALYZER "Enable Pothos Plotters.LogicAnalyzer component" ON "ENABLE_PLOTTERS" OFF) 5 | add_feature_info(" Logic Analyzer" ENABLE_PLOTTERS_LOGICANALYZER "Logic analyzer list-view debug display") 6 | if (NOT ENABLE_PLOTTERS_LOGICANALYZER) 7 | return() 8 | endif() 9 | 10 | ######################################################################## 11 | # Build logic analyzer plot module 12 | ######################################################################## 13 | POTHOS_MODULE_UTIL( 14 | TARGET LogicAnalyzer 15 | SOURCES 16 | LogicAnalyzer.cpp 17 | LogicAnalyzerDisplay.cpp 18 | DOC_SOURCES LogicAnalyzer.cpp 19 | LIBRARIES 20 | Qt${QT_VERSION_MAJOR}::Widgets 21 | PothosPlotterUtils 22 | DESTINATION plotters 23 | ) 24 | -------------------------------------------------------------------------------- /LogicAnalyzer/LogicAnalyzer.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2015 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include "LogicAnalyzerDisplay.hpp" 5 | #include 6 | #include 7 | #include 8 | 9 | /*********************************************************************** 10 | * |PothosDoc Logic Analyzer 11 | * 12 | * The logic analyzer displays discrete values of a signal over time. 13 | * 14 | * |category /Plotters 15 | * |keywords plot logic trace wave list view 16 | * |alias /widgets/logic_analyzer 17 | * 18 | * |param numInputs[Num Inputs] The number of input ports. 19 | * |default 1 20 | * |widget SpinBox(minimum=1) 21 | * |preview disable 22 | * 23 | * |param displayRate[Display Rate] How often the plotter updates. 24 | * |default 1.0 25 | * |units updates/sec 26 | * |preview disable 27 | * 28 | * |param sampleRate[Sample Rate] The rate of the input elements. 29 | * |default 1e6 30 | * |units samples/sec 31 | * 32 | * |param numPoints[Num Points] The number of points per plot capture. 33 | * |default 256 34 | * |preview disable 35 | * 36 | * |param align[Alignment] Ensure that multiple channels are aligned. 37 | * All channels must have matching sample rates when alignment is enabled. 38 | * |default true 39 | * |option [Disable] false 40 | * |option [Enable] true 41 | * 42 | * |param label0[Ch0 Label] The display label for channel 0. 43 | * |default "Ch0" 44 | * |widget StringEntry() 45 | * |preview disable 46 | * |tab Channels 47 | * 48 | * |param base0[Ch0 Base] The numeric base for channel 0. 49 | * |default 10 50 | * |option [Binary] 2 51 | * |option [Octal] 8 52 | * |option [Decimal] 10 53 | * |option [Hexadecimal] 16 54 | * |preview disable 55 | * |tab Channels 56 | * 57 | * |param label1[Ch1 Label] The display label for channel 1. 58 | * |default "Ch1" 59 | * |widget StringEntry() 60 | * |preview disable 61 | * |tab Channels 62 | * 63 | * |param base1[Ch1 Base] The numeric base for channel 1. 64 | * |default 10 65 | * |option [Binary] 2 66 | * |option [Octal] 8 67 | * |option [Decimal] 10 68 | * |option [Hexadecimal] 16 69 | * |preview disable 70 | * |tab Channels 71 | * 72 | * |param label2[Ch2 Label] The display label for channel 2. 73 | * |default "Ch2" 74 | * |widget StringEntry() 75 | * |preview disable 76 | * |tab Channels 77 | * 78 | * |param base2[Ch2 Base] The numeric base for channel 2. 79 | * |default 10 80 | * |option [Binary] 2 81 | * |option [Octal] 8 82 | * |option [Decimal] 10 83 | * |option [Hexadecimal] 16 84 | * |preview disable 85 | * |tab Channels 86 | * 87 | * |param label3[Ch2 Label] The display label for channel 3. 88 | * |default "Ch3" 89 | * |widget StringEntry() 90 | * |preview disable 91 | * |tab Channels 92 | * 93 | * |param base3[Ch3 Base] The numeric base for channel 3. 94 | * |default 10 95 | * |option [Binary] 2 96 | * |option [Octal] 8 97 | * |option [Decimal] 10 98 | * |option [Hexadecimal] 16 99 | * |preview disable 100 | * |tab Channels 101 | * 102 | * |param xAxisMode[X Axis Mode] Choose between index or time display. 103 | * |option [Index] "INDEX" 104 | * |option [Time] "TIME" 105 | * |default "INDEX" 106 | * |preview disable 107 | * |tab Axis 108 | * 109 | * |param rateLabelId[Rate Label ID] Labels with this ID can be used to set the sample rate. 110 | * To ignore sample rate labels, set this parameter to an empty string. 111 | * |default "rxRate" 112 | * |widget StringEntry() 113 | * |preview disable 114 | * |tab Labels 115 | * 116 | * |mode graphWidget 117 | * |factory /plotters/logic_analyzer(remoteEnv) 118 | * |initializer setNumInputs(numInputs) 119 | * |setter setDisplayRate(displayRate) 120 | * |setter setSampleRate(sampleRate) 121 | * |setter setNumPoints(numPoints) 122 | * |setter setAlignment(align) 123 | * |setter setChannelLabel(0, label0) 124 | * |setter setChannelBase(0, base0) 125 | * |setter setChannelLabel(1, label1) 126 | * |setter setChannelBase(1, base1) 127 | * |setter setChannelLabel(2, label2) 128 | * |setter setChannelBase(2, base2) 129 | * |setter setChannelLabel(3, label3) 130 | * |setter setChannelBase(3, base3) 131 | * |setter setXAxisMode(xAxisMode) 132 | * |setter setRateLabelId(rateLabelId) 133 | **********************************************************************/ 134 | class LogicAnalyzer : public Pothos::Topology 135 | { 136 | public: 137 | static Topology *make(const Pothos::ProxyEnvironment::Sptr &remoteEnv) 138 | { 139 | return new LogicAnalyzer(remoteEnv); 140 | } 141 | 142 | LogicAnalyzer(const Pothos::ProxyEnvironment::Sptr &remoteEnv) 143 | { 144 | _display.reset(new LogicAnalyzerDisplay()); 145 | _display->setName("Display"); 146 | 147 | auto registry = remoteEnv->findProxy("Pothos/BlockRegistry"); 148 | _trigger = registry.call("/comms/wave_trigger"); 149 | _trigger.call("setName", "Trigger"); 150 | _trigger.call("setMode", "PERIODIC"); 151 | 152 | //register calls in this topology 153 | this->registerCall(this, POTHOS_FCN_TUPLE(LogicAnalyzer, setNumInputs)); 154 | this->registerCall(this, POTHOS_FCN_TUPLE(LogicAnalyzer, setDisplayRate)); 155 | this->registerCall(this, POTHOS_FCN_TUPLE(LogicAnalyzer, setNumPoints)); 156 | this->registerCall(this, POTHOS_FCN_TUPLE(LogicAnalyzer, setAlignment)); 157 | this->registerCall(this, POTHOS_FCN_TUPLE(LogicAnalyzer, setRateLabelId)); 158 | 159 | //connect to internal display block 160 | this->connect(this, "setChannelLabel", _display, "setChannelLabel"); 161 | this->connect(this, "setChannelBase", _display, "setChannelBase"); 162 | this->connect(this, "setXAxisMode", _display, "setXAxisMode"); 163 | 164 | //connect to the internal snooper block 165 | this->connect(this, "setDisplayRate", _trigger, "setEventRate"); 166 | this->connect(this, "setNumPoints", _trigger, "setNumPoints"); 167 | this->connect(this, "setAlignment", _trigger, "setAlignment"); 168 | 169 | //connect stream ports 170 | this->connect(_trigger, 0, _display, 0); 171 | } 172 | 173 | Pothos::Object opaqueCallMethod(const std::string &name, const Pothos::Object *inputArgs, const size_t numArgs) const 174 | { 175 | //calls that go to the topology 176 | try 177 | { 178 | return Pothos::Topology::opaqueCallMethod(name, inputArgs, numArgs); 179 | } 180 | catch (const Pothos::BlockCallNotFound &){} 181 | 182 | //forward everything else to display 183 | return _display->opaqueCallMethod(name, inputArgs, numArgs); 184 | } 185 | 186 | void setNumInputs(const size_t numInputs) 187 | { 188 | _display->setNumInputs(numInputs); 189 | _trigger.call("setNumPorts", numInputs); 190 | for (size_t i = 0; i < numInputs; i++) 191 | { 192 | this->connect(this, i, _trigger, i); 193 | } 194 | } 195 | 196 | void setDisplayRate(const double rate) 197 | { 198 | _trigger.call("setEventRate", rate); 199 | } 200 | 201 | void setNumPoints(const size_t num) 202 | { 203 | _trigger.call("setNumPoints", num); 204 | } 205 | 206 | void setAlignment(const bool enabled) 207 | { 208 | _trigger.call("setAlignment", enabled); 209 | } 210 | 211 | void setRateLabelId(const std::string &id) 212 | { 213 | _display->setRateLabelId(id); 214 | std::vector ids; 215 | if (not id.empty()) ids.push_back(id); 216 | _trigger.call("setIdsList", ids); 217 | } 218 | 219 | private: 220 | Pothos::Proxy _trigger; 221 | std::shared_ptr _display; 222 | }; 223 | 224 | /*********************************************************************** 225 | * registration 226 | **********************************************************************/ 227 | static Pothos::BlockRegistry registerLogicAnalyzer( 228 | "/plotters/logic_analyzer", &LogicAnalyzer::make); 229 | 230 | static Pothos::BlockRegistry registerLogicAnalyzerOldPath( 231 | "/widgets/logic_analyzer", &LogicAnalyzer::make); 232 | -------------------------------------------------------------------------------- /LogicAnalyzer/LogicAnalyzerDisplay.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include "LogicAnalyzerDisplay.hpp" 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | /*********************************************************************** 11 | * Number format overloads 12 | **********************************************************************/ 13 | static QString toStr(const std::complex num, const int) 14 | { 15 | if (num.real() == 0.0 and num.imag() == 0.0) return "0"; 16 | if (num.real() == 0.0) return QString::number(num.imag())+"j"; 17 | if (num.imag() == 0.0) return QString::number(num.real()); 18 | if (num.imag() < 0.0) return QString("%1%2j").arg(num.real()).arg(num.imag()); 19 | return QString("%1+%2j").arg(num.real()).arg(num.imag()); 20 | } 21 | 22 | static QString toStr(const qreal num, const int) 23 | { 24 | return QString::number(num); 25 | } 26 | 27 | static QString toStr(const qlonglong num, const int base) 28 | { 29 | return QString::number(num, base); 30 | } 31 | 32 | /*********************************************************************** 33 | * Logic analyzer GUI implementation 34 | **********************************************************************/ 35 | LogicAnalyzerDisplay::LogicAnalyzerDisplay(void): 36 | _tableView(new QTableWidget(this)), 37 | _sampleRate(1.0), 38 | _xAxisMode("INDEX"), 39 | _rateLabelId("rxRate") 40 | { 41 | auto layout = new QHBoxLayout(this); 42 | layout->addWidget(_tableView); 43 | 44 | //setup block 45 | this->registerCall(this, POTHOS_FCN_TUPLE(LogicAnalyzerDisplay, widget)); 46 | this->registerCall(this, POTHOS_FCN_TUPLE(LogicAnalyzerDisplay, setNumInputs)); 47 | this->registerCall(this, POTHOS_FCN_TUPLE(LogicAnalyzerDisplay, numInputs)); 48 | this->registerCall(this, POTHOS_FCN_TUPLE(LogicAnalyzerDisplay, setSampleRate)); 49 | this->registerCall(this, POTHOS_FCN_TUPLE(LogicAnalyzerDisplay, sampleRate)); 50 | this->registerCall(this, POTHOS_FCN_TUPLE(LogicAnalyzerDisplay, setChannelLabel)); 51 | this->registerCall(this, POTHOS_FCN_TUPLE(LogicAnalyzerDisplay, setChannelBase)); 52 | this->registerCall(this, POTHOS_FCN_TUPLE(LogicAnalyzerDisplay, setXAxisMode)); 53 | this->registerCall(this, POTHOS_FCN_TUPLE(LogicAnalyzerDisplay, setRateLabelId)); 54 | this->setupInput(0); 55 | 56 | //register types passed to gui thread from work 57 | qRegisterMetaType("Pothos::Packet"); 58 | } 59 | 60 | LogicAnalyzerDisplay::~LogicAnalyzerDisplay(void) 61 | { 62 | return; 63 | } 64 | 65 | void LogicAnalyzerDisplay::setNumInputs(const size_t numInputs) 66 | { 67 | _chLabel.resize(numInputs); 68 | _chBase.resize(numInputs, 10); 69 | _chData.resize(numInputs); 70 | _tableView->setRowCount(numInputs); 71 | } 72 | 73 | void LogicAnalyzerDisplay::setSampleRate(const double sampleRate) 74 | { 75 | _sampleRate = sampleRate; 76 | QMetaObject::invokeMethod(this, "handleReplot", Qt::QueuedConnection); 77 | } 78 | 79 | template 80 | void LogicAnalyzerDisplay::populateChannel(const int channel, const Pothos::Packet &packet) 81 | { 82 | //convert buffer (does not convert when type matches) 83 | const auto numericBuff = packet.payload.convert(typeid(T)); 84 | assert(int(_chData.size()) > channel); 85 | _chData[channel] = packet; 86 | _chData[channel].payload = numericBuff; 87 | 88 | //load element data into table 89 | for (size_t i = 0; i < numericBuff.elements(); i++) 90 | { 91 | const auto num = numericBuff.as()[i]; 92 | const auto s = toStr(num, _chBase.at(channel)); 93 | auto item = new QTableWidgetItem(s); 94 | auto flags = item->flags(); 95 | flags &= ~Qt::ItemIsEditable; 96 | item->setFlags(flags); 97 | item->setTextAlignment(Qt::AlignRight); 98 | _tableView->setItem(channel, i, item); 99 | } 100 | 101 | //inspect labels to decorate table 102 | for (const auto &label : packet.labels) 103 | { 104 | const int column = label.index; 105 | assert(column < _tableView->columnCount()); 106 | auto item = _tableView->item(channel, column); 107 | 108 | //highlight and display label id 109 | item->setBackground(Qt::yellow); 110 | item->setText(QString("%1\n%2") 111 | .arg(item->text()) 112 | .arg(QString::fromStdString(label.id))); 113 | _tableView->resizeColumnToContents(column); 114 | } 115 | } 116 | 117 | void LogicAnalyzerDisplay::updateData(const Pothos::Packet &packet) 118 | { 119 | const auto indexIt = packet.metadata.find("index"); 120 | const auto channel = (indexIt == packet.metadata.end())?0:indexIt->second.convert(); 121 | 122 | //column count changed? new labels 123 | const size_t numElems = packet.payload.elements(); 124 | const bool changed = _tableView->columnCount() != int(numElems); 125 | _tableView->setColumnCount(numElems); 126 | if (changed) this->updateHeaders(); 127 | 128 | const auto dtype = packet.payload.dtype; 129 | if (dtype.isComplex()) this->populateChannel>(channel, packet); 130 | else if (dtype.isFloat()) this->populateChannel(channel, packet); 131 | else if (dtype.isInteger()) this->populateChannel(channel, packet); 132 | 133 | if (changed) _tableView->resizeColumnsToContents(); 134 | } 135 | 136 | void LogicAnalyzerDisplay::updateHeaders(void) 137 | { 138 | const size_t numElems = _tableView->columnCount(); 139 | 140 | double factor = 1.0; 141 | QString units("s"); 142 | double timeSpan = numElems/_sampleRate; 143 | if (timeSpan <= 100e-9) 144 | { 145 | factor = 1e9; 146 | units = "ns"; 147 | } 148 | else if (timeSpan <= 100e-6) 149 | { 150 | factor = 1e6; 151 | units = "us"; 152 | } 153 | else if (timeSpan <= 100e-3) 154 | { 155 | factor = 1e3; 156 | units = "ms"; 157 | } 158 | 159 | if (_xAxisMode == "INDEX") for (size_t i = 0; i < numElems; i++) 160 | { 161 | _tableView->setHorizontalHeaderItem(i, new QTableWidgetItem(QString::number(i))); 162 | } 163 | 164 | if (_xAxisMode == "TIME") for (size_t i = 0; i < numElems; i++) 165 | { 166 | double t = i*_sampleRate/factor; 167 | _tableView->setHorizontalHeaderItem(i, new QTableWidgetItem(QString::number(t)+units)); 168 | } 169 | } 170 | 171 | void LogicAnalyzerDisplay::handleReplot(void) 172 | { 173 | _tableView->clear(); 174 | this->updateHeaders(); 175 | 176 | for (size_t ch = 0; ch < this->numInputs(); ch++) 177 | { 178 | auto label = _chLabel.at(ch); 179 | if (label.isEmpty()) label = tr("Ch%1").arg(ch); 180 | _tableView->setVerticalHeaderItem(ch, new QTableWidgetItem(label)); 181 | this->updateData(_chData.at(ch)); 182 | } 183 | 184 | _tableView->resizeColumnsToContents(); 185 | } 186 | 187 | void LogicAnalyzerDisplay::work(void) 188 | { 189 | auto inPort = this->input(0); 190 | 191 | if (not inPort->hasMessage()) return; 192 | const auto msg = inPort->popMessage(); 193 | 194 | //label-based messages have in-line commands 195 | if (msg.type() == typeid(Pothos::Label)) 196 | { 197 | const auto &label = msg.convert(); 198 | if (label.id == _rateLabelId and label.data.canConvert(typeid(double))) 199 | { 200 | this->setSampleRate(label.data.convert()); 201 | } 202 | } 203 | 204 | //packet-based messages have payloads to display 205 | if (msg.type() == typeid(Pothos::Packet)) 206 | { 207 | const auto &packet = msg.convert(); 208 | QMetaObject::invokeMethod(this, "updateData", Qt::QueuedConnection, Q_ARG(Pothos::Packet, packet)); 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /LogicAnalyzer/LogicAnalyzerDisplay.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2015 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #pragma once 5 | #include 6 | #include 7 | 8 | class QTableWidget; 9 | 10 | class LogicAnalyzerDisplay : public QWidget, public Pothos::Block 11 | { 12 | Q_OBJECT 13 | public: 14 | 15 | LogicAnalyzerDisplay(void); 16 | 17 | ~LogicAnalyzerDisplay(void); 18 | 19 | void setNumInputs(const size_t numInputs); 20 | 21 | /*! 22 | * sample rate for the plotter 23 | * controls the time scaling display 24 | */ 25 | void setSampleRate(const double sampleRate); 26 | 27 | size_t numInputs(void) const 28 | { 29 | return _chData.size(); 30 | } 31 | 32 | double sampleRate(void) const 33 | { 34 | return _sampleRate; 35 | } 36 | 37 | QWidget *widget(void) 38 | { 39 | return this; 40 | } 41 | 42 | //allow for standard resize controls with the default size policy 43 | QSize minimumSizeHint(void) const 44 | { 45 | return QSize(300, 100); 46 | } 47 | QSize sizeHint(void) const 48 | { 49 | return this->minimumSizeHint(); 50 | } 51 | 52 | void setXAxisMode(const std::string &mode) 53 | { 54 | _xAxisMode = mode; 55 | QMetaObject::invokeMethod(this, "handleReplot", Qt::QueuedConnection); 56 | } 57 | 58 | void setRateLabelId(const std::string &id) 59 | { 60 | _rateLabelId = id; 61 | } 62 | 63 | void setChannelLabel(const size_t ch, const QString &label) 64 | { 65 | if (_chLabel.size() <= ch) _chLabel.resize(ch+1); 66 | _chLabel[ch] = label; 67 | QMetaObject::invokeMethod(this, "handleReplot", Qt::QueuedConnection); 68 | } 69 | 70 | void setChannelBase(const size_t ch, const size_t base) 71 | { 72 | if (_chBase.size() <= ch) _chBase.resize(ch+1); 73 | _chBase[ch] = base; 74 | QMetaObject::invokeMethod(this, "handleReplot", Qt::QueuedConnection); 75 | } 76 | 77 | void work(void); 78 | 79 | private slots: 80 | void updateData(const Pothos::Packet &); 81 | void updateHeaders(void); 82 | void handleReplot(void); 83 | 84 | private: 85 | 86 | template 87 | void populateChannel(const int channel, const Pothos::Packet &); 88 | 89 | QTableWidget *_tableView; 90 | 91 | double _sampleRate; 92 | std::string _xAxisMode; 93 | std::string _rateLabelId; 94 | 95 | //per-channel settings 96 | std::vector _chLabel; 97 | std::vector _chBase; 98 | std::vector _chData; 99 | }; 100 | 101 | -------------------------------------------------------------------------------- /Periodogram/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ######################################################################## 2 | ## Feature registration 3 | ######################################################################## 4 | cmake_dependent_option(ENABLE_PLOTTERS_PERIODOGRAM "Enable Pothos Plotters.Periodogram component" ON "Spuce_FOUND;ENABLE_PLOTTERS" OFF) 5 | add_feature_info(" Periodogram" ENABLE_PLOTTERS_PERIODOGRAM "Periodogram frequency-domain FFT plotter") 6 | if (NOT ENABLE_PLOTTERS_PERIODOGRAM) 7 | return() 8 | endif() 9 | 10 | ######################################################################## 11 | # Build frequency domain plot module 12 | ######################################################################## 13 | POTHOS_MODULE_UTIL( 14 | TARGET Periodogram 15 | SOURCES 16 | Periodogram.cpp 17 | PeriodogramWork.cpp 18 | PeriodogramChannel.cpp 19 | PeriodogramDisplay.cpp 20 | DOC_SOURCES Periodogram.cpp 21 | LIBRARIES 22 | Qt${QT_VERSION_MAJOR}::Widgets 23 | PothosQwt 24 | PothosPlotterUtils 25 | spuce 26 | DESTINATION plotters 27 | ) 28 | -------------------------------------------------------------------------------- /Periodogram/Periodogram.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2017 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include "PeriodogramDisplay.hpp" 5 | #include 6 | #include 7 | #include 8 | 9 | /*********************************************************************** 10 | * |PothosDoc Periodogram 11 | * 12 | * The periodogram plot displays a live two dimensional plot of power vs frequency. 13 | * 14 | * |category /Plotters 15 | * |keywords frequency plot fft dft spectrum spectral 16 | * |alias /widgets/periodogram 17 | * 18 | * |param title The title of the plot 19 | * |default "Power vs Frequency" 20 | * |widget StringEntry() 21 | * |preview valid 22 | * 23 | * |param numInputs[Num Inputs] The number of input ports. 24 | * |default 1 25 | * |widget SpinBox(minimum=1) 26 | * |preview disable 27 | * 28 | * |param displayRate[Display Rate] How often the plotter updates. 29 | * |default 10.0 30 | * |units updates/sec 31 | * |preview disable 32 | * 33 | * |param sampleRate[Sample Rate] The rate of the input elements. 34 | * |default 1e6 35 | * |units samples/sec 36 | * 37 | * |param centerFreq[Center Freq] The center frequency of the plot. 38 | * This value controls the labeling of the horizontal access. 39 | * |default 0.0 40 | * |units Hz 41 | * |preview valid 42 | * 43 | * |param numBins[Num FFT Bins] The number of bins per fourier transform. 44 | * |default 1024 45 | * |option 512 46 | * |option 1024 47 | * |option 2048 48 | * |option 4096 49 | * |widget ComboBox(editable=true) 50 | * |preview disable 51 | * |tab FFT 52 | * 53 | * |param window[Window Type] The window function controls passband ripple. 54 | * |default "hann" 55 | * |option [Rectangular] "rectangular" 56 | * |option [Hann] "hann" 57 | * |option [Hamming] "hamming" 58 | * |option [Blackman] "blackman" 59 | * |option [Bartlett] "bartlett" 60 | * |option [Flat-top] "flattop" 61 | * |option [Kaiser] "kaiser" 62 | * |option [Chebyshev] "chebyshev" 63 | * |preview disable 64 | * |tab FFT 65 | * 66 | * |param windowArgs[Window Args] Optional window arguments (depends on window type). 67 | *
    68 | *
  • When using the Kaiser window, specify [beta] to use the parameterized Kaiser window.
  • 69 | *
  • When using the Chebyshev window, specify [atten] to use the Dolph-Chebyshev window with attenuation in dB.
  • 70 | *
71 | * |default [] 72 | * |preview disable 73 | * |tab FFT 74 | * 75 | * |param fullScale[Full Scale] The amplitude that corresponds to full-scale. 76 | * A full-scale amplitude signal will appear as 0.0 dBfs on the plotter. 77 | * The default value of 1.0 works best for scaled floating point samples. 78 | * A signed 16-bit integer value might use 32768 as full-scale instead. 79 | * |default 1.0 80 | * |preview disable 81 | * |tab FFT 82 | * 83 | * |param fftMode[FFT Mode] Power spectrum display mode. 84 | *
    85 | *
  • Real mode ("REAL") displays only the positive frequencies between [0, +fs/2].
  • 86 | *
  • Complex mode ("COMPLEX) displays positive and negative frequencies between [-fs/2, +fs/2].
  • 87 | *
  • Automatic mode ("AUTO") selects the FFT mode based on the data type of the current signal.
  • 88 | *
89 | * |default "AUTO" 90 | * |option [Automatic] "AUTO" 91 | * |option [Complex] "COMPLEX" 92 | * |option [Real] "REAL" 93 | * |preview disable 94 | * |tab FFT 95 | * 96 | * |param autoScale[Auto-Scale] Enable automatic scaling for the vertical axis. 97 | * |default false 98 | * |option [Auto scale] true 99 | * |option [Use limits] false 100 | * |preview disable 101 | * |tab Axis 102 | * 103 | * |param refLevel[Reference Level] The maximum displayable power level. 104 | * |default 0.0 105 | * |units dBfs 106 | * |widget DoubleSpinBox(minimum=-150, maximum=150, step=10, decimals=1) 107 | * |preview disable 108 | * |tab Axis 109 | * 110 | * |param dynRange[Dynamic Range] The ratio of largest to smallest displayable power level. 111 | * The vertical axis will display values from the ref level to ref level - dynamic range. 112 | * |default 100.0 113 | * |units dB 114 | * |widget DoubleSpinBox(minimum=10, maximum=200, step=10, decimals=1) 115 | * |preview disable 116 | * |tab Axis 117 | * 118 | * |param averaging[Averaging] Averaging factor for moving average over FFT bins. 119 | * A factor of 0.0 means no averaging. 120 | * A factor of 1.0 means max averaging. 121 | * Increasing the value increases the averaging window. 122 | * |default 0.0 123 | * |preview disable 124 | * |widget DoubleSpinBox(minimum=0.0, maximum=1.0, step=0.05, decimals=3) 125 | * 126 | * |param enableXAxis[Enable X-Axis] Show or hide the horizontal axis markers. 127 | * |option [Show] true 128 | * |option [Hide] false 129 | * |default true 130 | * |preview disable 131 | * |tab Axis 132 | * 133 | * |param enableYAxis[Enable Y-Axis] Show or hide the vertical axis markers. 134 | * |option [Show] true 135 | * |option [Hide] false 136 | * |default true 137 | * |preview disable 138 | * |tab Axis 139 | * 140 | * |param yAxisTitle[Y-Axis Title] The title of the verical axis. 141 | * |default "dB" 142 | * |widget StringEntry() 143 | * |preview disable 144 | * |tab Axis 145 | * 146 | * |param freqLabelId[Freq Label ID] Labels with this ID can be used to set the center frequency. 147 | * To ignore frequency labels, set this parameter to an empty string. 148 | * |default "rxFreq" 149 | * |widget StringEntry() 150 | * |preview disable 151 | * |tab Labels 152 | * 153 | * |param rateLabelId[Rate Label ID] Labels with this ID can be used to set the sample rate. 154 | * To ignore sample rate labels, set this parameter to an empty string. 155 | * |default "rxRate" 156 | * |widget StringEntry() 157 | * |preview disable 158 | * |tab Labels 159 | * 160 | * |param startLabelId[Start Label ID] Align captured input to the specified label ID. 161 | * An empty label ID disables this feature. 162 | * |default "" 163 | * |widget StringEntry() 164 | * |preview disable 165 | * |tab Labels 166 | * 167 | * |mode graphWidget 168 | * |factory /plotters/periodogram(remoteEnv) 169 | * |initializer setNumInputs(numInputs) 170 | * |setter setTitle(title) 171 | * |setter setDisplayRate(displayRate) 172 | * |setter setSampleRate(sampleRate) 173 | * |setter setCenterFrequency(centerFreq) 174 | * |setter setNumFFTBins(numBins) 175 | * |setter setWindowType(window, windowArgs) 176 | * |setter setFullScale(fullScale) 177 | * |setter setFFTMode(fftMode) 178 | * |setter setAutoScale(autoScale) 179 | * |setter setReferenceLevel(refLevel) 180 | * |setter setDynamicRange(dynRange) 181 | * |setter setAverageFactor(averaging) 182 | * |setter enableXAxis(enableXAxis) 183 | * |setter enableYAxis(enableYAxis) 184 | * |setter setYAxisTitle(yAxisTitle) 185 | * |setter setFreqLabelId(freqLabelId) 186 | * |setter setRateLabelId(rateLabelId) 187 | * |setter setStartLabelId(startLabelId) 188 | **********************************************************************/ 189 | class Periodogram : public Pothos::Topology 190 | { 191 | public: 192 | static Topology *make(const Pothos::ProxyEnvironment::Sptr &remoteEnv) 193 | { 194 | return new Periodogram(remoteEnv); 195 | } 196 | 197 | Periodogram(const Pothos::ProxyEnvironment::Sptr &remoteEnv) 198 | { 199 | _display.reset(new PeriodogramDisplay()); 200 | _display->setName("Display"); 201 | 202 | auto registry = remoteEnv->findProxy("Pothos/BlockRegistry"); 203 | _trigger = registry.call("/comms/wave_trigger"); 204 | _trigger.call("setName", "Trigger"); 205 | _trigger.call("setMode", "PERIODIC"); 206 | 207 | //register calls in this topology 208 | this->registerCall(this, POTHOS_FCN_TUPLE(Periodogram, setNumInputs)); 209 | this->registerCall(this, POTHOS_FCN_TUPLE(Periodogram, setDisplayRate)); 210 | this->registerCall(this, POTHOS_FCN_TUPLE(Periodogram, setNumFFTBins)); 211 | this->registerCall(this, POTHOS_FCN_TUPLE(Periodogram, setFreqLabelId)); 212 | this->registerCall(this, POTHOS_FCN_TUPLE(Periodogram, setRateLabelId)); 213 | this->registerCall(this, POTHOS_FCN_TUPLE(Periodogram, setStartLabelId)); 214 | 215 | //connect to internal display block 216 | this->connect(this, "setTitle", _display, "setTitle"); 217 | this->connect(this, "setSampleRate", _display, "setSampleRate"); 218 | this->connect(this, "setCenterFrequency", _display, "setCenterFrequency"); 219 | this->connect(this, "setNumFFTBins", _display, "setNumFFTBins"); 220 | this->connect(this, "setWindowType", _display, "setWindowType"); 221 | this->connect(this, "setFullScale", _display, "setFullScale"); 222 | this->connect(this, "setFFTMode", _display, "setFFTMode"); 223 | this->connect(this, "setReferenceLevel", _display, "setReferenceLevel"); 224 | this->connect(this, "setDynamicRange", _display, "setDynamicRange"); 225 | this->connect(this, "setAutoScale", _display, "setAutoScale"); 226 | this->connect(this, "setAverageFactor", _display, "setAverageFactor"); 227 | this->connect(this, "enableXAxis", _display, "enableXAxis"); 228 | this->connect(this, "enableYAxis", _display, "enableYAxis"); 229 | this->connect(this, "setYAxisTitle", _display, "setYAxisTitle"); 230 | this->connect(this, "clearChannels", _display, "clearChannels"); 231 | this->connect(_display, "frequencySelected", this, "frequencySelected"); 232 | this->connect(_display, "relativeFrequencySelected", this, "relativeFrequencySelected"); 233 | 234 | //connect to the internal snooper block 235 | this->connect(this, "setDisplayRate", _trigger, "setEventRate"); 236 | this->connect(this, "setNumFFTBins", _trigger, "setNumPoints"); 237 | 238 | //connect stream ports 239 | this->connect(_trigger, 0, _display, 0); 240 | 241 | } 242 | 243 | Pothos::Object opaqueCallMethod(const std::string &name, const Pothos::Object *inputArgs, const size_t numArgs) const 244 | { 245 | //calls that go to the topology 246 | try 247 | { 248 | return Pothos::Topology::opaqueCallMethod(name, inputArgs, numArgs); 249 | } 250 | catch (const Pothos::BlockCallNotFound &){} 251 | 252 | //forward everything else to display 253 | return _display->opaqueCallMethod(name, inputArgs, numArgs); 254 | } 255 | 256 | void setNumInputs(const size_t numInputs) 257 | { 258 | _trigger.call("setNumPorts", numInputs); 259 | for (size_t i = 0; i < numInputs; i++) 260 | { 261 | this->connect(this, i, _trigger, i); 262 | } 263 | } 264 | 265 | void setDisplayRate(const double rate) 266 | { 267 | _trigger.call("setEventRate", rate); 268 | } 269 | 270 | void setNumFFTBins(const size_t num) 271 | { 272 | _trigger.call("setNumPoints", num); 273 | _display->setNumFFTBins(num); 274 | } 275 | 276 | void setFreqLabelId(const std::string &id) 277 | { 278 | _display->setFreqLabelId(id); 279 | _freqLabelId = id; 280 | this->updateIdsList(); 281 | } 282 | 283 | void setRateLabelId(const std::string &id) 284 | { 285 | _display->setRateLabelId(id); 286 | _rateLabelId = id; 287 | this->updateIdsList(); 288 | } 289 | 290 | void setStartLabelId(const std::string &id) 291 | { 292 | _trigger.call("setLabelId", id); 293 | _trigger.call("setMode", id.empty()?"PERIODIC":"NORMAL"); 294 | } 295 | 296 | void updateIdsList(void) 297 | { 298 | std::vector ids; 299 | if (not _freqLabelId.empty()) ids.push_back(_freqLabelId); 300 | if (not _rateLabelId.empty()) ids.push_back(_rateLabelId); 301 | _trigger.call("setIdsList", ids); 302 | } 303 | 304 | private: 305 | Pothos::Proxy _trigger; 306 | std::shared_ptr _display; 307 | std::string _freqLabelId, _rateLabelId; 308 | }; 309 | 310 | /*********************************************************************** 311 | * registration 312 | **********************************************************************/ 313 | static Pothos::BlockRegistry registerPeriodogram( 314 | "/plotters/periodogram", &Periodogram::make); 315 | 316 | static Pothos::BlockRegistry registerPeriodogramOldPath( 317 | "/widgets/periodogram", &Periodogram::make); 318 | -------------------------------------------------------------------------------- /Periodogram/PeriodogramChannel.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2016 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include "PeriodogramChannel.hpp" 5 | #include "PothosPlotter.hpp" 6 | #include "PothosPlotUtils.hpp" 7 | #include 8 | #include 9 | #include 10 | #include //min/max 11 | 12 | template 13 | T movingAvgPowerBinFilter(const T alpha, const T prev, const T curr) 14 | { 15 | return 10*std::log((1-alpha)*std::exp(prev/10) + alpha*std::exp(curr/10)); 16 | } 17 | 18 | PeriodogramChannel::PeriodogramChannel(const size_t index, PothosPlotter *plot) 19 | { 20 | _channelCurve.reset(new QwtPlotCurve(QString("Ch%1").arg(index))); 21 | _maxHoldCurve.reset(new QwtPlotCurve(QString("Max%1").arg(index))); 22 | _minHoldCurve.reset(new QwtPlotCurve(QString("Min%1").arg(index))); 23 | 24 | _channelCurve->setPen(pastelize(getDefaultCurveColor(index))); 25 | 26 | auto minColor = pastelize(getDefaultCurveColor(2*index+8+0)); 27 | auto maxColor = pastelize(getDefaultCurveColor(2*index+8+1)); 28 | minColor.setAlphaF(0.5); 29 | maxColor.setAlphaF(0.5); 30 | 31 | _maxHoldCurve->setPen(minColor); 32 | _minHoldCurve->setPen(maxColor); 33 | 34 | _maxHoldCurve->setVisible(false); 35 | _minHoldCurve->setVisible(false); 36 | 37 | _channelCurve->attach(plot); 38 | _maxHoldCurve->attach(plot); 39 | _minHoldCurve->attach(plot); 40 | 41 | plot->updateChecked(_channelCurve.get()); 42 | plot->updateChecked(_maxHoldCurve.get()); 43 | plot->updateChecked(_minHoldCurve.get()); 44 | } 45 | 46 | PeriodogramChannel::~PeriodogramChannel(void) 47 | { 48 | return; 49 | } 50 | 51 | void PeriodogramChannel::update(const std::valarray &powerBins, const double rate, const double freq, const double factor) 52 | { 53 | //scale (0.0 to 1.0) to log10(1.0 to 10.0) = 0.0 to 1.0 54 | //alpha has a reversed log-scale effect on the averaging 55 | const float alpha = 1 - float(std::log10(9*factor + 1)); 56 | 57 | initBufferSize(powerBins, _channelBuffer); 58 | initBufferSize(powerBins, _maxHoldBuffer); 59 | initBufferSize(powerBins, _minHoldBuffer); 60 | 61 | for (size_t i = 0; i < powerBins.size(); i++) 62 | { 63 | auto x = (rate*i)/(powerBins.size()-1) - rate/2 + freq; 64 | _channelBuffer[i] = QPointF(x, movingAvgPowerBinFilter(alpha, _channelBuffer[i].y(), powerBins[i])); 65 | _maxHoldBuffer[i] = QPointF(x, std::max(_maxHoldBuffer[i].y(), powerBins[i])); 66 | _minHoldBuffer[i] = QPointF(x, std::min(_minHoldBuffer[i].y(), powerBins[i])); 67 | } 68 | 69 | _channelCurve->setSamples(_channelBuffer); 70 | _maxHoldCurve->setSamples(_maxHoldBuffer); 71 | _minHoldCurve->setSamples(_minHoldBuffer); 72 | } 73 | 74 | void PeriodogramChannel::clearOnChange(QwtPlotItem *item) 75 | { 76 | if (item == _maxHoldCurve.get()) _maxHoldBuffer.clear(); 77 | if (item == _minHoldCurve.get()) _minHoldBuffer.clear(); 78 | } 79 | 80 | void PeriodogramChannel::initBufferSize(const std::valarray &powerBins, QVector &buff) 81 | { 82 | if (size_t(buff.size()) == powerBins.size()) return; 83 | buff.clear(); 84 | buff.resize(powerBins.size()); 85 | for (size_t i = 0; i < powerBins.size(); i++) 86 | { 87 | buff[i] = QPointF(0, powerBins[i]); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Periodogram/PeriodogramChannel.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2016 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #pragma once 5 | #include //_USE_MATH_DEFINES 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | class PothosPlotter; 13 | class QwtPlotCurve; 14 | class QwtPlotItem; 15 | 16 | class PeriodogramChannel : QObject 17 | { 18 | Q_OBJECT 19 | public: 20 | PeriodogramChannel(const size_t index, PothosPlotter *plot); 21 | 22 | ~PeriodogramChannel(void); 23 | 24 | void update(const std::valarray &powerBins, const double rate, const double freq, const double factor); 25 | 26 | void clearOnChange(QwtPlotItem *item); 27 | 28 | private: 29 | 30 | void initBufferSize(const std::valarray &powerBins, QVector &buff); 31 | 32 | QVector _channelBuffer; 33 | QVector _maxHoldBuffer; 34 | QVector _minHoldBuffer; 35 | std::unique_ptr _channelCurve; 36 | std::unique_ptr _maxHoldCurve; 37 | std::unique_ptr _minHoldCurve; 38 | }; 39 | -------------------------------------------------------------------------------- /Periodogram/PeriodogramDisplay.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2021 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include "PeriodogramDisplay.hpp" 5 | #include "PeriodogramChannel.hpp" 6 | #include "PothosPlotter.hpp" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include //min/max 14 | 15 | PeriodogramDisplay::PeriodogramDisplay(void): 16 | _mainPlot(new PothosPlotter(this, POTHOS_PLOTTER_GRID | POTHOS_PLOTTER_ZOOM)), 17 | _sampleRate(1.0), 18 | _sampleRateWoAxisUnits(1.0), 19 | _centerFreq(0.0), 20 | _centerFreqWoAxisUnits(0.0), 21 | _numBins(1024), 22 | _refLevel(0.0), 23 | _dynRange(100.0), 24 | _autoScale(false), 25 | _freqLabelId("rxFreq"), 26 | _rateLabelId("rxRate"), 27 | _averageFactor(0.0), 28 | _fullScale(1.0), 29 | _fftModeComplex(true), 30 | _fftModeAutomatic(true) 31 | { 32 | //setup block 33 | this->registerCall(this, POTHOS_FCN_TUPLE(PeriodogramDisplay, widget)); 34 | this->registerCall(this, POTHOS_FCN_TUPLE(PeriodogramDisplay, setTitle)); 35 | this->registerCall(this, POTHOS_FCN_TUPLE(PeriodogramDisplay, setSampleRate)); 36 | this->registerCall(this, POTHOS_FCN_TUPLE(PeriodogramDisplay, setCenterFrequency)); 37 | this->registerCall(this, POTHOS_FCN_TUPLE(PeriodogramDisplay, setNumFFTBins)); 38 | this->registerCall(this, POTHOS_FCN_TUPLE(PeriodogramDisplay, setWindowType)); 39 | this->registerCall(this, POTHOS_FCN_TUPLE(PeriodogramDisplay, setFullScale)); 40 | this->registerCall(this, POTHOS_FCN_TUPLE(PeriodogramDisplay, setFFTMode)); 41 | this->registerCall(this, POTHOS_FCN_TUPLE(PeriodogramDisplay, setReferenceLevel)); 42 | this->registerCall(this, POTHOS_FCN_TUPLE(PeriodogramDisplay, setDynamicRange)); 43 | this->registerCall(this, POTHOS_FCN_TUPLE(PeriodogramDisplay, setAutoScale)); 44 | this->registerCall(this, POTHOS_FCN_TUPLE(PeriodogramDisplay, title)); 45 | this->registerCall(this, POTHOS_FCN_TUPLE(PeriodogramDisplay, sampleRate)); 46 | this->registerCall(this, POTHOS_FCN_TUPLE(PeriodogramDisplay, centerFrequency)); 47 | this->registerCall(this, POTHOS_FCN_TUPLE(PeriodogramDisplay, numFFTBins)); 48 | this->registerCall(this, POTHOS_FCN_TUPLE(PeriodogramDisplay, referenceLevel)); 49 | this->registerCall(this, POTHOS_FCN_TUPLE(PeriodogramDisplay, dynamicRange)); 50 | this->registerCall(this, POTHOS_FCN_TUPLE(PeriodogramDisplay, autoScale)); 51 | this->registerCall(this, POTHOS_FCN_TUPLE(PeriodogramDisplay, setAverageFactor)); 52 | this->registerCall(this, POTHOS_FCN_TUPLE(PeriodogramDisplay, enableXAxis)); 53 | this->registerCall(this, POTHOS_FCN_TUPLE(PeriodogramDisplay, enableYAxis)); 54 | this->registerCall(this, POTHOS_FCN_TUPLE(PeriodogramDisplay, setYAxisTitle)); 55 | this->registerCall(this, POTHOS_FCN_TUPLE(PeriodogramDisplay, setFreqLabelId)); 56 | this->registerCall(this, POTHOS_FCN_TUPLE(PeriodogramDisplay, setRateLabelId)); 57 | this->registerCall(this, POTHOS_FCN_TUPLE(PeriodogramDisplay, clearChannels)); 58 | this->registerSlot("clearChannels"); 59 | this->registerSignal("frequencySelected"); 60 | this->registerSignal("relativeFrequencySelected"); 61 | this->setupInput(0); 62 | 63 | //layout 64 | auto layout = new QHBoxLayout(this); 65 | layout->setSpacing(0); 66 | layout->setContentsMargins(QMargins()); 67 | layout->addWidget(_mainPlot); 68 | 69 | //setup plotter 70 | { 71 | connect(_mainPlot->zoomer(), QOverload::of(&QwtPlotZoomer::selected), this, &PeriodogramDisplay::handlePickerSelected); 72 | connect(_mainPlot->zoomer(), &QwtPlotZoomer::zoomed, this, &PeriodogramDisplay::handleZoomed); 73 | 74 | auto legend = new QwtLegend(_mainPlot); 75 | legend->setDefaultItemMode(QwtLegendData::Checkable); 76 | connect(legend, &QwtLegend::checked, this, &PeriodogramDisplay::handleLegendChecked); 77 | _mainPlot->insertLegend(legend); 78 | } 79 | } 80 | 81 | PeriodogramDisplay::~PeriodogramDisplay(void) 82 | { 83 | return; 84 | } 85 | 86 | void PeriodogramDisplay::setTitle(const QString &title) 87 | { 88 | QMetaObject::invokeMethod(_mainPlot, "setTitle", Qt::QueuedConnection, Q_ARG(QString, title)); 89 | } 90 | 91 | void PeriodogramDisplay::setSampleRate(const double sampleRate) 92 | { 93 | _sampleRate = sampleRate; 94 | QMetaObject::invokeMethod(this, "handleUpdateAxis", Qt::QueuedConnection); 95 | } 96 | 97 | void PeriodogramDisplay::setCenterFrequency(const double freq) 98 | { 99 | _centerFreq = freq; 100 | QMetaObject::invokeMethod(this, "handleUpdateAxis", Qt::QueuedConnection); 101 | } 102 | 103 | void PeriodogramDisplay::setNumFFTBins(const size_t numBins) 104 | { 105 | _numBins = numBins; 106 | } 107 | 108 | void PeriodogramDisplay::setWindowType(const std::string &windowType, const std::vector &windowArgs) 109 | { 110 | _fftPowerSpectrum.setWindowType(windowType, windowArgs); 111 | } 112 | 113 | void PeriodogramDisplay::setFullScale(const double fullScale) 114 | { 115 | _fullScale = fullScale; 116 | } 117 | 118 | void PeriodogramDisplay::setFFTMode(const std::string &fftMode) 119 | { 120 | if (fftMode == "REAL"){} 121 | else if (fftMode == "COMPLEX"){} 122 | else if (fftMode == "AUTO"){} 123 | else throw Pothos::InvalidArgumentException("PeriodogramDisplay::setFFTMode("+fftMode+")", "unknown mode"); 124 | _fftModeComplex = (fftMode != "REAL"); 125 | _fftModeAutomatic = (fftMode == "AUTO"); 126 | QMetaObject::invokeMethod(this, "handleUpdateAxis", Qt::QueuedConnection); 127 | } 128 | 129 | void PeriodogramDisplay::setReferenceLevel(const double refLevel) 130 | { 131 | _refLevel = refLevel; 132 | QMetaObject::invokeMethod(this, "handleUpdateAxis", Qt::QueuedConnection); 133 | } 134 | 135 | void PeriodogramDisplay::setDynamicRange(const double dynRange) 136 | { 137 | _dynRange = dynRange; 138 | QMetaObject::invokeMethod(this, "handleUpdateAxis", Qt::QueuedConnection); 139 | } 140 | 141 | void PeriodogramDisplay::setAutoScale(const bool autoScale) 142 | { 143 | _autoScale = autoScale; 144 | QMetaObject::invokeMethod(this, "handleUpdateAxis", Qt::QueuedConnection); 145 | } 146 | 147 | void PeriodogramDisplay::handleUpdateAxis(void) 148 | { 149 | QString axisTitle("Hz"); 150 | double factor = std::max(_sampleRate, _centerFreq); 151 | if (factor >= 2e9) 152 | { 153 | factor = 1e9; 154 | axisTitle = "GHz"; 155 | } 156 | else if (factor >= 2e6) 157 | { 158 | factor = 1e6; 159 | axisTitle = "MHz"; 160 | } 161 | else if (factor >= 2e3) 162 | { 163 | factor = 1e3; 164 | axisTitle = "kHz"; 165 | } 166 | _mainPlot->setAxisTitle(QwtPlot::xBottom, axisTitle); 167 | 168 | _mainPlot->zoomer()->setAxis(QwtPlot::xBottom, QwtPlot::yLeft); 169 | _sampleRateWoAxisUnits = _sampleRate/factor; 170 | _centerFreqWoAxisUnits = _centerFreq/factor; 171 | const qreal freqLow = _fftModeComplex?(_centerFreqWoAxisUnits-_sampleRateWoAxisUnits/2):0.0; 172 | _mainPlot->setAxisScale(QwtPlot::xBottom, freqLow, _centerFreqWoAxisUnits+_sampleRateWoAxisUnits/2); 173 | _mainPlot->setAxisScale(QwtPlot::yLeft, _refLevel-_dynRange, _refLevel); 174 | _mainPlot->updateAxes(); //update after axis changes 175 | _mainPlot->zoomer()->setZoomBase(); //record current axis settings 176 | this->handleZoomed(_mainPlot->zoomer()->zoomBase()); //reload 177 | } 178 | 179 | QVariant PeriodogramDisplay::saveState(void) const 180 | { 181 | auto map = _mainPlot->state().toMap(); 182 | map["isComplex"] = _fftModeComplex; 183 | return map; 184 | } 185 | 186 | void PeriodogramDisplay::restoreState(const QVariant &state) 187 | { 188 | const auto map = state.toMap(); 189 | const auto isComplex = map.constFind("isComplex"); 190 | if (isComplex != map.constEnd()) _fftModeComplex = isComplex.value().toBool(); 191 | _mainPlot->setState(state); 192 | } 193 | 194 | void PeriodogramDisplay::handleZoomed(const QRectF &rect) 195 | { 196 | //when zoomed all the way out, return to autoscale 197 | if (rect == _mainPlot->zoomer()->zoomBase() and _autoScale) 198 | { 199 | _mainPlot->setAxisAutoScale(QwtPlot::yLeft); 200 | } 201 | } 202 | 203 | void PeriodogramDisplay::handleClearChannels(void) 204 | { 205 | _curves.clear(); 206 | } 207 | 208 | QString PeriodogramDisplay::title(void) const 209 | { 210 | return _mainPlot->title().text(); 211 | } 212 | 213 | void PeriodogramDisplay::enableXAxis(const bool enb) 214 | { 215 | QMetaObject::invokeMethod(_mainPlot, "enableAxis", Qt::QueuedConnection, Q_ARG(int, QwtPlot::xBottom), Q_ARG(bool, enb)); 216 | } 217 | 218 | void PeriodogramDisplay::enableYAxis(const bool enb) 219 | { 220 | QMetaObject::invokeMethod(_mainPlot, "enableAxis", Qt::QueuedConnection, Q_ARG(int, QwtPlot::yLeft), Q_ARG(bool, enb)); 221 | } 222 | 223 | void PeriodogramDisplay::setYAxisTitle(const QString &title) 224 | { 225 | QMetaObject::invokeMethod(_mainPlot, "setAxisTitle", Qt::QueuedConnection, Q_ARG(int, QwtPlot::yLeft), Q_ARG(QString, title)); 226 | } 227 | 228 | void PeriodogramDisplay::handlePickerSelected(const QPointF &p) 229 | { 230 | const double freq = p.x()*_sampleRate/_sampleRateWoAxisUnits; 231 | this->emitSignal("frequencySelected", freq); 232 | this->emitSignal("relativeFrequencySelected", freq - _centerFreq); 233 | } 234 | 235 | void PeriodogramDisplay::handleLegendChecked(const QVariant &itemInfo, bool on, int) 236 | { 237 | auto item = _mainPlot->infoToItem(itemInfo); 238 | for (const auto &c : _curves) 239 | { 240 | if (item->isVisible() != on) 241 | c.second->clearOnChange(item); 242 | } 243 | item->setVisible(on); 244 | _mainPlot->replot(); 245 | } 246 | -------------------------------------------------------------------------------- /Periodogram/PeriodogramDisplay.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2017 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #pragma once 5 | #include //_USE_MATH_DEFINES 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "PothosPlotterFFTUtils.hpp" 14 | 15 | class PothosPlotter; 16 | class QwtPlotCurve; 17 | class PeriodogramChannel; 18 | 19 | class PeriodogramDisplay : public QWidget, public Pothos::Block 20 | { 21 | Q_OBJECT 22 | public: 23 | 24 | PeriodogramDisplay(void); 25 | 26 | ~PeriodogramDisplay(void); 27 | 28 | QWidget *widget(void) 29 | { 30 | return this; 31 | } 32 | 33 | //! set the plotter's title 34 | void setTitle(const QString &title); 35 | 36 | /*! 37 | * sample rate for the plotter 38 | * controls the frequency scaling display 39 | */ 40 | void setSampleRate(const double sampleRate); 41 | 42 | /*! 43 | * center frequency of the plot 44 | */ 45 | void setCenterFrequency(const double freq); 46 | 47 | void setNumFFTBins(const size_t numBins); 48 | void setWindowType(const std::string &, const std::vector &); 49 | void setFullScale(const double fullScale); 50 | void setFFTMode(const std::string &fftMode); 51 | void setReferenceLevel(const double refLevel); 52 | void setDynamicRange(const double dynRange); 53 | void setAutoScale(const bool autoScale); 54 | 55 | QString title(void) const; 56 | 57 | double sampleRate(void) const 58 | { 59 | return _sampleRate; 60 | } 61 | 62 | double centerFrequency(void) const 63 | { 64 | return _centerFreq; 65 | } 66 | 67 | size_t numFFTBins(void) const 68 | { 69 | return _numBins; 70 | } 71 | 72 | double referenceLevel(void) const 73 | { 74 | return _refLevel; 75 | } 76 | 77 | double dynamicRange(void) const 78 | { 79 | return _dynRange; 80 | } 81 | 82 | bool autoScale(void) const 83 | { 84 | return _autoScale; 85 | } 86 | 87 | void enableXAxis(const bool enb); 88 | void enableYAxis(const bool enb); 89 | void setYAxisTitle(const QString &title); 90 | 91 | void clearChannels(void) 92 | { 93 | QMetaObject::invokeMethod(this, "handleClearChannels", Qt::QueuedConnection); 94 | } 95 | 96 | void setFreqLabelId(const std::string &id) 97 | { 98 | _freqLabelId = id; 99 | } 100 | 101 | void setRateLabelId(const std::string &id) 102 | { 103 | _rateLabelId = id; 104 | } 105 | 106 | void setAverageFactor(const double factor) 107 | { 108 | if (factor > 1.0 or factor < 0.0) throw Pothos::RangeException( 109 | "Periodogram::setAverageFactor("+std::to_string(factor)+")", 110 | "factor must be in [1.0, 0.0]"); 111 | _averageFactor = factor; 112 | } 113 | 114 | void work(void); 115 | 116 | //allow for standard resize controls with the default size policy 117 | QSize minimumSizeHint(void) const 118 | { 119 | return QSize(300, 150); 120 | } 121 | QSize sizeHint(void) const 122 | { 123 | return this->minimumSizeHint(); 124 | } 125 | 126 | public slots: 127 | 128 | QVariant saveState(void) const; 129 | 130 | void restoreState(const QVariant &value); 131 | 132 | private slots: 133 | void handlePickerSelected(const QPointF &); 134 | void handlePowerBins(const int index, const std::valarray &bins); 135 | void handleUpdateAxis(void); 136 | void handleZoomed(const QRectF &rect); 137 | void handleClearChannels(void); 138 | void handleLegendChecked(const QVariant &, bool, int); 139 | 140 | private: 141 | PothosPlotter *_mainPlot; 142 | FFTPowerSpectrum _fftPowerSpectrum; 143 | double _sampleRate; 144 | double _sampleRateWoAxisUnits; 145 | double _centerFreq; 146 | double _centerFreqWoAxisUnits; 147 | size_t _numBins; 148 | double _refLevel; 149 | double _dynRange; 150 | bool _autoScale; 151 | std::string _freqLabelId; 152 | std::string _rateLabelId; 153 | double _averageFactor; 154 | double _fullScale; 155 | bool _fftModeComplex; 156 | bool _fftModeAutomatic; 157 | 158 | //per-port data structs 159 | std::map> _curves; 160 | std::map>> _queueDepth; 161 | }; 162 | -------------------------------------------------------------------------------- /Periodogram/PeriodogramWork.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2017 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include "PeriodogramDisplay.hpp" 5 | #include "PeriodogramChannel.hpp" 6 | #include "PothosPlotter.hpp" 7 | #include 8 | #include 9 | #include 10 | 11 | /*********************************************************************** 12 | * work functions 13 | **********************************************************************/ 14 | void PeriodogramDisplay::handlePowerBins(const int index, const std::valarray &powerBins) 15 | { 16 | if (_queueDepth.at(index)->fetch_sub(1) != 1) return; 17 | 18 | auto &curve = _curves[index]; 19 | if (not curve) curve.reset(new PeriodogramChannel(index, _mainPlot)); 20 | curve->update(powerBins, _sampleRateWoAxisUnits, _centerFreqWoAxisUnits, _averageFactor); 21 | _mainPlot->replot(); 22 | } 23 | 24 | void PeriodogramDisplay::work(void) 25 | { 26 | auto inPort = this->input(0); 27 | 28 | if (not inPort->hasMessage()) return; 29 | const auto msg = inPort->popMessage(); 30 | 31 | //label-based messages have in-line commands 32 | if (msg.type() == typeid(Pothos::Label)) 33 | { 34 | const auto &label = msg.convert(); 35 | if (label.id == _freqLabelId and label.data.canConvert(typeid(double))) 36 | { 37 | this->setCenterFrequency(label.data.convert()); 38 | } 39 | if (label.id == _rateLabelId and label.data.canConvert(typeid(double))) 40 | { 41 | this->setSampleRate(label.data.convert()); 42 | } 43 | } 44 | 45 | //packet-based messages have payloads to FFT 46 | if (msg.type() == typeid(Pothos::Packet)) 47 | { 48 | const auto &packet = msg.convert(); 49 | const auto indexIt = packet.metadata.find("index"); 50 | const auto index = (indexIt == packet.metadata.end())?0:indexIt->second.convert(); 51 | const auto &buff = packet.payload; 52 | std::valarray powerBins; 53 | 54 | //handle automatic FFT mode 55 | if (_fftModeAutomatic and index == 0) 56 | { 57 | const bool isComplex = buff.dtype.isComplex(); 58 | const bool changed = _fftModeComplex != isComplex; 59 | _fftModeComplex = isComplex; 60 | if (changed) QMetaObject::invokeMethod(this, "handleUpdateAxis", Qt::QueuedConnection); 61 | } 62 | 63 | //support payloads that are already transformed into a power spectrum 64 | const auto formatIt = packet.metadata.find("format"); 65 | if (formatIt != packet.metadata.end() and 66 | formatIt->second.canConvert(typeid(std::string)) and 67 | formatIt->second.convert() == "POWER_BINS") 68 | { 69 | auto floatBuff = buff.convert(Pothos::DType(typeid(float)), buff.elements()); 70 | powerBins = std::valarray(floatBuff.as(), floatBuff.elements()); 71 | } 72 | 73 | //power bins to points on the curve 74 | else 75 | { 76 | //safe guard against FFT size changes, old buffers could still be in-flight 77 | if (buff.elements() != this->numFFTBins()) return; 78 | auto floatBuff = buff.convert(Pothos::DType(typeid(std::complex)), buff.elements()); 79 | CArray fftBins(floatBuff.as *>(), this->numFFTBins()); 80 | powerBins = _fftPowerSpectrum.transform(fftBins, _fullScale); 81 | } 82 | 83 | if (not _queueDepth[index]) _queueDepth[index].reset(new std::atomic(0)); 84 | _queueDepth[index]->fetch_add(1); 85 | QMetaObject::invokeMethod(this, "handlePowerBins", Qt::QueuedConnection, Q_ARG(int, index), Q_ARG(std::valarray, powerBins)); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /PlotterUtils/PlotUtilsConfig.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2015 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | #if defined(POTHOS_PLOTTER_UTILS_MAKEDLL) // create a DLL library 10 | #define POTHOS_PLOTTER_UTILS_EXPORT Q_DECL_EXPORT 11 | #else // use a DLL library 12 | #define POTHOS_PLOTTER_UTILS_EXPORT Q_DECL_IMPORT 13 | #endif 14 | -------------------------------------------------------------------------------- /PlotterUtils/PothosPlotPicker.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2021 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include "PothosPlotPicker.hpp" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include //min/max 11 | 12 | PothosPlotPicker::PothosPlotPicker(QWidget *parent): 13 | QwtPlotZoomer(QwtPlot::xBottom, QwtPlot::yLeft, parent), 14 | _raster(nullptr) 15 | { 16 | this->setMousePattern(QwtEventPattern::MouseSelect2, Qt::RightButton, Qt::ControlModifier); 17 | this->setMousePattern(QwtEventPattern::MouseSelect3, Qt::RightButton); 18 | this->setTrackerMode(QwtPicker::AlwaysOn); 19 | }; 20 | 21 | void PothosPlotPicker::registerRaster(QwtRasterData *raster) 22 | { 23 | _raster = raster; 24 | } 25 | 26 | void PothosPlotPicker::widgetMouseDoubleClickEvent(QMouseEvent *event) 27 | { 28 | emit this->selected(this->invTransform(event->pos())); 29 | } 30 | 31 | bool PothosPlotPicker::accept(QPolygon &pa) const 32 | { 33 | if (pa.count() < 2) return false; 34 | 35 | auto rect = QRect(pa[0], pa[int(pa.count()) - 1]).normalized(); 36 | const auto canvas = qobject_cast(this->plot()->canvas()); 37 | assert(canvas != nullptr); 38 | const auto size = canvas->frameRect().size(); 39 | const auto pad = canvas->frameWidth(); 40 | 41 | rect.setTopLeft(QPoint( 42 | std::max(pad, rect.topLeft().x()), 43 | std::max(pad, rect.topLeft().y()))); 44 | 45 | rect.setBottomRight(QPoint( 46 | std::min(size.width()-pad-1, rect.bottomRight().x()), 47 | std::min(size.height()-pad-1, rect.bottomRight().y()))); 48 | 49 | pa.resize(2); 50 | pa[0] = rect.topLeft(); 51 | pa[1] = rect.bottomRight(); 52 | 53 | return QwtPlotZoomer::accept(pa); 54 | } 55 | 56 | QwtText PothosPlotPicker::trackerTextF(const QPointF &pos) const 57 | { 58 | QString zvalue; 59 | if (_raster != nullptr and 60 | this->plot()->axisInterval(this->xAxis()).contains(pos.x()) and 61 | this->plot()->axisInterval(this->yAxis()).contains(pos.y()) 62 | ) zvalue = QString("
%1 %2") 63 | .arg(_raster->value(pos.x(), pos.y())) 64 | .arg(this->plot()->axisTitle(QwtPlot::yRight).text().toHtmlEscaped()); 65 | 66 | QwtText text(QString("%1 %2
%3 %4%5
") 67 | .arg(pos.x()) 68 | .arg(this->plot()->axisTitle(this->xAxis()).text().toHtmlEscaped()) 69 | .arg(pos.y()) 70 | .arg(this->plot()->axisTitle(this->yAxis()).text().toHtmlEscaped()) 71 | .arg(zvalue)); 72 | 73 | static const QColor paleYellow("#FFFFCC"); 74 | text.setBackgroundBrush(QBrush(paleYellow)); 75 | return text; 76 | } 77 | -------------------------------------------------------------------------------- /PlotterUtils/PothosPlotPicker.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2016 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #pragma once 5 | #include "PlotUtilsConfig.hpp" 6 | #include 7 | 8 | class QwtRasterData; 9 | 10 | /*! 11 | * Plot picker with custom marker style 12 | */ 13 | class POTHOS_PLOTTER_UTILS_EXPORT PothosPlotPicker : public QwtPlotZoomer 14 | { 15 | Q_OBJECT 16 | public: 17 | PothosPlotPicker(QWidget *parent); 18 | 19 | //! support for getting a z-axis value 20 | void registerRaster(QwtRasterData *raster); 21 | 22 | protected: 23 | QwtText trackerTextF(const QPointF &pos) const; 24 | void widgetMouseDoubleClickEvent(QMouseEvent *); 25 | bool accept(QPolygon &pa) const; 26 | 27 | private: 28 | QwtRasterData *_raster; 29 | }; 30 | -------------------------------------------------------------------------------- /PlotterUtils/PothosPlotStyler.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2016 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #pragma once 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | static inline QFont PothosMarkerLabelFont(void) 13 | { 14 | QFont f; 15 | f.setPointSize(7); 16 | f.setWeight(QFont::DemiBold); 17 | f.setStretch(QFont::SemiExpanded); 18 | return f; 19 | } 20 | 21 | static inline QwtText PothosMarkerLabel(const QString &label) 22 | { 23 | QwtText text(label); 24 | static const QFont font(PothosMarkerLabelFont()); 25 | text.setFont(font); 26 | static const QColor lightGray("#D0D0D0"); 27 | text.setBackgroundBrush(QBrush(lightGray)); 28 | return text; 29 | } 30 | -------------------------------------------------------------------------------- /PlotterUtils/PothosPlotUtils.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2016 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include "PothosPlotUtils.hpp" 5 | #include "PothosPlotStyler.hpp" 6 | #include "PothosPlotPicker.hpp" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | QColor getDefaultCurveColor(const size_t whichCurve) 17 | { 18 | switch(whichCurve % 12) 19 | { 20 | case 0: return Qt::blue; 21 | case 1: return Qt::green; 22 | case 2: return Qt::red; 23 | case 3: return Qt::cyan; 24 | case 4: return Qt::magenta; 25 | case 5: return Qt::yellow; 26 | case 6: return Qt::darkBlue; 27 | case 7: return Qt::darkGreen; 28 | case 8: return Qt::darkRed; 29 | case 9: return Qt::darkCyan; 30 | case 10: return Qt::darkMagenta; 31 | case 11: return Qt::darkYellow; 32 | }; 33 | return QColor(); 34 | } 35 | 36 | QColor pastelize(const QColor &c) 37 | { 38 | //Pastels have high value and low to intermediate saturation: 39 | //http://en.wikipedia.org/wiki/Pastel_%28color%29 40 | return QColor::fromHsv(c.hue(), int(c.saturationF()*128), int(c.valueF()*64)+191); 41 | } 42 | -------------------------------------------------------------------------------- /PlotterUtils/PothosPlotUtils.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2016 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #pragma once 5 | #include "PlotUtilsConfig.hpp" 6 | #include 7 | 8 | //! Get a color for a plotter curve given an index 9 | POTHOS_PLOTTER_UTILS_EXPORT QColor getDefaultCurveColor(const size_t whichCurve); 10 | 11 | //! make a color have pastel-properties 12 | POTHOS_PLOTTER_UTILS_EXPORT QColor pastelize(const QColor &c); 13 | -------------------------------------------------------------------------------- /PlotterUtils/PothosPlotter.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2021 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include "PothosPlotter.hpp" 5 | #include "PothosPlotPicker.hpp" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | /*********************************************************************** 17 | * Custom Fonts for styling 18 | **********************************************************************/ 19 | static QFont PothosPlotAxisFont(void) 20 | { 21 | QFont f; 22 | f.setPointSize(7); 23 | f.setWeight(QFont::Normal); 24 | f.setStretch(QFont::SemiCondensed); 25 | return f; 26 | } 27 | 28 | static QFont PothosPlotAxisTitleFont(void) 29 | { 30 | QFont f; 31 | f.setPointSize(7); 32 | f.setWeight(QFont::DemiBold); 33 | f.setStretch(QFont::SemiExpanded); 34 | return f; 35 | } 36 | 37 | static QFont PothosPlotTitleFont(void) 38 | { 39 | QFont f; 40 | f.setPointSize(8); 41 | f.setWeight(QFont::Bold); 42 | f.setStretch(QFont::SemiExpanded); 43 | return f; 44 | } 45 | 46 | /*********************************************************************** 47 | * Custom QwtPlotCanvas that accepts the mousePressEvent 48 | * (needed to make mouse-based events work within QGraphicsItem) 49 | **********************************************************************/ 50 | class PothosPlotterCanvas : public QwtPlotCanvas 51 | { 52 | Q_OBJECT 53 | public: 54 | PothosPlotterCanvas(QwtPlot *parent): 55 | QwtPlotCanvas(parent) 56 | { 57 | return; 58 | } 59 | protected: 60 | void mousePressEvent(QMouseEvent *event) 61 | { 62 | QwtPlotCanvas::mousePressEvent(event); 63 | event->accept(); 64 | } 65 | }; 66 | 67 | /*********************************************************************** 68 | * Custom QwtPlot implementation 69 | **********************************************************************/ 70 | PothosPlotter::PothosPlotter(QWidget *parent, const int enables): 71 | QwtPlot(parent), 72 | _zoomer(nullptr), 73 | _grid(nullptr) 74 | { 75 | //setup canvas 76 | this->setCanvas(new PothosPlotterCanvas(this)); 77 | auto bgColor = this->palette().color(QPalette::Background); 78 | 79 | //When the default background is light, force the plot 80 | //background to be white because it generally looks better; 81 | //otherwise leave the color default to support dark themes. 82 | if (bgColor.lightnessF() > 0.5) bgColor = Qt::white; 83 | this->setCanvasBackground(QBrush(bgColor)); 84 | 85 | //font style 86 | this->setAxisFont(QwtPlot::xBottom, PothosPlotAxisFont()); 87 | this->setAxisFont(QwtPlot::yLeft, PothosPlotAxisFont()); 88 | this->setAxisFont(QwtPlot::yRight, PothosPlotAxisFont()); 89 | 90 | //setup optional plot zoomer 91 | if ((enables & POTHOS_PLOTTER_ZOOM) != 0) 92 | { 93 | _zoomer = new PothosPlotPicker(this->canvas()); 94 | } 95 | 96 | //setup optional plot grid 97 | if ((enables & POTHOS_PLOTTER_GRID) != 0) 98 | { 99 | auto plotGrid = new QwtPlotGrid(); 100 | plotGrid->attach(this); 101 | plotGrid->setPen(QPen(QColor("#999999"), 0.5, Qt::DashLine)); 102 | } 103 | 104 | //connections 105 | qRegisterMetaType>("QList"); //missing from qwt 106 | qRegisterMetaType>("std::valarray"); //used for plot data 107 | connect(this, &PothosPlotter::itemAttached, this, &PothosPlotter::handleItemAttached); 108 | } 109 | 110 | PothosPlotter::~PothosPlotter(void) 111 | { 112 | delete _zoomer; 113 | delete _grid; 114 | } 115 | 116 | void PothosPlotter::setTitle(const QString &text) 117 | { 118 | static const QFont font(PothosPlotTitleFont()); 119 | QwtText t(text); 120 | t.setFont(font); 121 | QwtPlot::setTitle(t); 122 | } 123 | 124 | void PothosPlotter::setAxisTitle(const int id, const QString &text) 125 | { 126 | static const QFont font(PothosPlotAxisTitleFont()); 127 | QwtText t(text); 128 | t.setFont(font); 129 | QwtPlot::setAxisTitle(id, t); 130 | } 131 | 132 | void PothosPlotter::enableAxis(const int axisId, const bool tf) 133 | { 134 | QwtPlot::enableAxis(axisId, tf); 135 | } 136 | 137 | void PothosPlotter::updateChecked(QwtPlotItem *item) 138 | { 139 | auto legend = qobject_cast(this->legend()); 140 | if (legend == nullptr) return; //no legend 141 | auto info = legend->legendWidget(this->itemToInfo(item)); 142 | auto label = qobject_cast(info); 143 | if (label == nullptr) return; //no label 144 | label->setChecked(item->isVisible()); 145 | this->updateLegend(); 146 | } 147 | 148 | void PothosPlotter::handleItemAttached(QwtPlotItem *item, bool on) 149 | { 150 | //only cuves are supported by this logic 151 | if (item->rtti() != QwtPlotItem::Rtti_PlotCurve) return; 152 | 153 | const auto items = this->itemList(); 154 | if (on) 155 | { 156 | const int i = items.size()-1; 157 | 158 | //apply stashed visibility when the item count matches 159 | //this handles curves which are added on-demand 160 | if (_visible.size() > i) 161 | { 162 | items[i]->setVisible(_visible.at(i)); 163 | this->updateChecked(items[i]); 164 | } 165 | 166 | //otherwise expand the cache to match the item size 167 | else _visible.resize(items.size()); 168 | } 169 | 170 | else 171 | { 172 | //store the visibility status when removed 173 | //so it can be restored when added back 174 | if (items.size() < _visible.size()) 175 | _visible.setBit(items.size(), item->isVisible()); 176 | } 177 | } 178 | 179 | QVariant PothosPlotter::state(void) const 180 | { 181 | QVariantMap state; 182 | 183 | //zoom stack 184 | QVariantList stack; 185 | for (const auto &rect : zoomer()->zoomStack()) stack.append(rect); 186 | state["stack"] = stack; 187 | state["index"] = zoomer()->zoomRectIndex(); 188 | 189 | //item visibility 190 | const auto items = this->itemList(); 191 | QBitArray visible(_visible); 192 | for (int i = 0; i < items.size() and i < _visible.size(); i++) 193 | { 194 | visible.setBit(i, items[i]->isVisible()); 195 | } 196 | state["visible"] = visible; 197 | 198 | return state; 199 | } 200 | 201 | void PothosPlotter::setState(const QVariant &state) 202 | { 203 | const auto map = state.toMap(); 204 | 205 | //zoom stack 206 | QStack stack; 207 | for (const auto &rect : map["stack"].toList()) stack.push(rect.toRectF()); 208 | zoomer()->setZoomStack(stack, map["index"].toInt()); 209 | 210 | //item visibility 211 | const auto items = this->itemList(); 212 | _visible = map["visible"].toBitArray(); 213 | for (int i = 0; i < items.size() and i < _visible.size(); i++) 214 | { 215 | if (items[i]->rtti() != QwtPlotItem::Rtti_PlotCurve) continue; 216 | items[i]->setVisible(_visible.at(i)); 217 | this->updateChecked(items[i]); 218 | } 219 | } 220 | 221 | #include "PothosPlotter.moc" 222 | -------------------------------------------------------------------------------- /PlotterUtils/PothosPlotter.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2017 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #pragma once 5 | #include "PlotUtilsConfig.hpp" 6 | #include 7 | #include 8 | #include 9 | 10 | class QwtPlotItem; 11 | class QwtPlotZoomer; 12 | class QwtPlotGrid; 13 | 14 | #define POTHOS_PLOTTER_GRID (1 << 0) 15 | #define POTHOS_PLOTTER_ZOOM (1 << 1) 16 | 17 | /*! 18 | * A QwtPlot extension class that has slots for certain things. 19 | */ 20 | class POTHOS_PLOTTER_UTILS_EXPORT PothosPlotter : public QwtPlot 21 | { 22 | Q_OBJECT 23 | public: 24 | PothosPlotter(QWidget *parent, const int enables = 0); 25 | 26 | ~PothosPlotter(void); 27 | 28 | //! update checked status based on visibility 29 | void updateChecked(QwtPlotItem *item); 30 | 31 | //! query the plot state as a QVariant 32 | QVariant state(void) const; 33 | 34 | //! restore the state from QVariant 35 | void setState(const QVariant &state); 36 | 37 | //! Get the plot zoomer for this canvas 38 | QwtPlotZoomer *zoomer(void) const 39 | { 40 | return _zoomer; 41 | } 42 | 43 | public slots: 44 | void setTitle(const QString &text); 45 | void setAxisTitle(const int id, const QString &text); 46 | void enableAxis(const int axisId, const bool tf = true); 47 | 48 | private slots: 49 | void handleItemAttached(QwtPlotItem *plotItem, bool on); 50 | 51 | private: 52 | QwtPlotZoomer *_zoomer; 53 | QwtPlotGrid *_grid; 54 | QBitArray _visible; 55 | }; 56 | -------------------------------------------------------------------------------- /PlotterUtils/PothosPlotterFFTUtils.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2016 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #pragma once 5 | #include "PlotUtilsConfig.hpp" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | //////////////////////////////////////////////////////////////////////// 15 | //FFT code can be found at: 16 | //http://rosettacode.org/wiki/Fast_Fourier_transform 17 | //////////////////////////////////////////////////////////////////////// 18 | typedef std::complex Complex; 19 | typedef std::valarray CArray; 20 | 21 | // Cooley–Tukey FFT (in-place) 22 | inline void fft(CArray& x) 23 | { 24 | const size_t N = x.size(); 25 | if (N <= 1) return; 26 | 27 | // divide 28 | CArray even = x[std::slice(0, N/2, 2)]; 29 | CArray odd = x[std::slice(1, N/2, 2)]; 30 | 31 | // conquer 32 | fft(even); 33 | fft(odd); 34 | 35 | // combine 36 | for (size_t k = 0; k < N/2; ++k) 37 | { 38 | Complex t = std::polar(1.0f, -2 * float(M_PI) * k / N) * odd[k]; 39 | x[k ] = even[k] + t; 40 | x[k+N/2] = even[k] - t; 41 | } 42 | } 43 | 44 | // inverse fft (in-place) 45 | inline void ifft(CArray& x) 46 | { 47 | // conjugate the complex numbers 48 | x = x.apply(std::conj); 49 | 50 | // forward fft 51 | fft( x ); 52 | 53 | // conjugate the complex numbers again 54 | x = x.apply(std::conj); 55 | 56 | // scale the numbers 57 | x /= x.size(); 58 | } 59 | 60 | //////////////////////////////////////////////////////////////////////// 61 | //FFT Power spectrum 62 | //////////////////////////////////////////////////////////////////////// 63 | struct FFTPowerSpectrum 64 | { 65 | void setWindowType(const std::string &windowType, const std::vector &windowArgs) 66 | { 67 | _windowType = windowType; 68 | _windowArgs = windowArgs; 69 | _precomputedWindow.clear(); 70 | } 71 | 72 | std::valarray transform(CArray &fftBins, const double fullScale = 1.0) 73 | { 74 | //windowing 75 | if (_precomputedWindow.size() != fftBins.size()) 76 | { 77 | _precomputedWindow = spuce::design_window(_windowType, fftBins.size(), _windowArgs.empty()?0.0:_windowArgs.at(0)); 78 | _precomputedWindowPower = 0.0; 79 | for (size_t n = 0; n < _precomputedWindow.size(); n++) 80 | { 81 | _precomputedWindowPower += _precomputedWindow[n]*_precomputedWindow[n]; 82 | } 83 | _precomputedWindowPower = std::sqrt(_precomputedWindowPower/_precomputedWindow.size()); 84 | } 85 | for (size_t n = 0; n < fftBins.size(); n++) fftBins[n] *= _precomputedWindow[n]; 86 | 87 | //take fft 88 | fft(fftBins); 89 | 90 | //window and fft gain adjustment 91 | const float gain_dB = 20*std::log10(fftBins.size()) + 20*std::log10(_precomputedWindowPower) + 20*std::log10(fullScale); 92 | 93 | //power calculation 94 | std::valarray powerBins(fftBins.size()); 95 | for (size_t i = 0; i < fftBins.size(); i++) 96 | { 97 | const float norm = std::max(std::norm(fftBins[i]), 1e-20f); 98 | powerBins[i] = 10*std::log10(norm) - gain_dB; 99 | } 100 | 101 | //bin reorder 102 | for (size_t i = 0; i < powerBins.size()/2; i++) 103 | { 104 | std::swap(powerBins[i], powerBins[i+powerBins.size()/2]); 105 | } 106 | 107 | return powerBins; 108 | } 109 | 110 | std::string _windowType; 111 | std::vector _windowArgs; 112 | std::vector _precomputedWindow; 113 | double _precomputedWindowPower; 114 | }; 115 | -------------------------------------------------------------------------------- /QwtWidgets/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ######################################################################## 2 | ## Feature registration 3 | ######################################################################## 4 | cmake_dependent_option(ENABLE_PLOTTERS_QWTWIDGETS "Enable Pothos Plotters.QwtWidgets component" ON "ENABLE_PLOTTERS" OFF) 5 | add_feature_info(" Qwt Widgets" ENABLE_PLOTTERS_QWTWIDGETS "Miscellaneous widgets based around Qwt") 6 | if (NOT ENABLE_PLOTTERS_QWTWIDGETS) 7 | return() 8 | endif() 9 | 10 | ######################################################################## 11 | # Build Qwt widgets module 12 | ######################################################################## 13 | POTHOS_MODULE_UTIL( 14 | TARGET QwtWidgets 15 | SOURCES 16 | QwtSlider.cpp 17 | QwtThermo.cpp 18 | QwtKnob.cpp 19 | QwtDial.cpp 20 | ENABLE_DOCS 21 | LIBRARIES 22 | Qt${QT_VERSION_MAJOR}::Widgets 23 | PothosQwt 24 | DESTINATION plotters 25 | ) 26 | -------------------------------------------------------------------------------- /QwtWidgets/QwtDial.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2016 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | /*********************************************************************** 13 | * |PothosDoc QWT Dial 14 | * 15 | * The dial widget is a circular status indicator for a floating point value. 16 | * http://qwt.sourceforge.net/class_qwt_dial.html 17 | * 18 | * |category /Widgets 19 | * |keywords dial turn compass 20 | * 21 | * |param title The name of the value displayed by this widget 22 | * |default "My Dial" 23 | * |widget StringEntry() 24 | * 25 | * |param mode The dial mode (rotate scale or rotate needle). 26 | * |default "RotateNeedle" 27 | * |option [Rotate Needle] "RotateNeedle" 28 | * |option [Rotate Scale] "RotateScale" 29 | * |preview disable 30 | * 31 | * |param needle The needle type (ray, arrow, compass, etc...). 32 | * |default "SimpleArrow" 33 | * |option [Simple Arrow] "SimpleArrow" 34 | * |option [Simple Ray] "SimpleRay" 35 | * |option [Compass Triangle] "CompassTriangle" 36 | * |option [Compass Thin] "CompassThin" 37 | * |option [Wind Arrow 1] "WindArrow1" 38 | * |option [Wind Arrow 2] "WindArrow2" 39 | * |preview disable 40 | * |tab Needle 41 | * 42 | * |param color1[Color 1] The first color for the dial's needle. 43 | * |widget ColorPicker() 44 | * |default "blue" 45 | * |preview disable 46 | * |tab Needle 47 | * 48 | * |param color2[Color 2] The second color for the dial's needle. 49 | * |widget ColorPicker() 50 | * |default "red" 51 | * |preview disable 52 | * |tab Needle 53 | * 54 | * |param scaleEngine[Scale Engine] The scaling engine, linear or logarithmic. 55 | * |default "Linear" 56 | * |option [Linear] "Linear" 57 | * |option [Log2] "Log2" 58 | * |option [Log10] "Log10" 59 | * |preview disable 60 | * 61 | * |param lowerBound[Lower Bound] The minimum value of this widget. 62 | * |default -1.0 63 | * |widget DoubleSpinBox() 64 | * 65 | * |param upperBound[Upper Bound] The maximum value of this widget. 66 | * |default 1.0 67 | * |widget DoubleSpinBox() 68 | * 69 | * |param stepSize[Step Size] The delta in-between discrete values in this widget. 70 | * Use a value of 0.0 for automatic step-size calculation. 71 | * |default 0.0 72 | * |widget DoubleSpinBox() 73 | * |preview valid 74 | * 75 | * |mode graphWidget 76 | * |factory /widgets/qwt_dial() 77 | * |setter setTitle(title) 78 | * |setter setMode(mode) 79 | * |setter setNeedle(needle, color1, color2) 80 | * |setter setScaleEngine(scaleEngine) 81 | * |setter setLowerBound(lowerBound) 82 | * |setter setUpperBound(upperBound) 83 | * |setter setStepSize(stepSize) 84 | **********************************************************************/ 85 | class QwtDialBlock : public QGroupBox, public Pothos::Block 86 | { 87 | Q_OBJECT 88 | public: 89 | 90 | static Block *make(void) 91 | { 92 | return new QwtDialBlock(); 93 | } 94 | 95 | QwtDialBlock(void): 96 | _dial(new QwtDial(this)) 97 | { 98 | auto layout = new QVBoxLayout(this); 99 | layout->setContentsMargins(QMargins()); 100 | layout->addWidget(_dial); 101 | this->setStyleSheet("QGroupBox {font-weight: bold;}"); 102 | 103 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtDialBlock, setTitle)); 104 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtDialBlock, setMode)); 105 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtDialBlock, setNeedle)); 106 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtDialBlock, widget)); 107 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtDialBlock, value)); 108 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtDialBlock, setValue)); 109 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtDialBlock, setLowerBound)); 110 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtDialBlock, setUpperBound)); 111 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtDialBlock, setStepSize)); 112 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtDialBlock, setScaleEngine)); 113 | } 114 | 115 | QWidget *widget(void) 116 | { 117 | return this; 118 | } 119 | 120 | void setTitle(const QString &title) 121 | { 122 | QMetaObject::invokeMethod(this, "handleSetTitle", Qt::QueuedConnection, Q_ARG(QString, title)); 123 | } 124 | 125 | void setMode(const QString &mode) 126 | { 127 | if (mode == "RotateNeedle") _dial->setMode(QwtDial::RotateNeedle); 128 | if (mode == "RotateScale") _dial->setMode(QwtDial::RotateScale); 129 | } 130 | 131 | void setNeedle(const QString &type, const QString &color1, const QString &color2) 132 | { 133 | if (type == "SimpleArrow") _dial->setNeedle(new QwtDialSimpleNeedle(QwtDialSimpleNeedle::Arrow, true, color1, color2)); 134 | if (type == "SimpleRay") _dial->setNeedle(new QwtDialSimpleNeedle(QwtDialSimpleNeedle::Ray, true, color1, color2)); 135 | if (type == "CompassTriangle") _dial->setNeedle(new QwtCompassMagnetNeedle(QwtCompassMagnetNeedle::TriangleStyle, color1, color2)); 136 | if (type == "CompassThin") _dial->setNeedle(new QwtCompassMagnetNeedle(QwtCompassMagnetNeedle::ThinStyle, color1, color2)); 137 | if (type == "WindArrow1") _dial->setNeedle(new QwtCompassWindArrow(QwtCompassWindArrow::Style1, color1, color2)); 138 | if (type == "WindArrow2") _dial->setNeedle(new QwtCompassWindArrow(QwtCompassWindArrow::Style2, color1, color2)); 139 | } 140 | 141 | double value(void) const 142 | { 143 | return _dial->value(); 144 | } 145 | 146 | void setValue(const double value) 147 | { 148 | _dial->setValue(value); 149 | } 150 | 151 | void setLowerBound(const double value) 152 | { 153 | _dial->setLowerBound(value); 154 | } 155 | 156 | void setUpperBound(const double value) 157 | { 158 | _dial->setUpperBound(value); 159 | } 160 | 161 | void setStepSize(const double step) 162 | { 163 | _dial->setScaleStepSize(step); 164 | } 165 | 166 | void setScaleEngine(const QString &engine) 167 | { 168 | if (engine == "Linear") _dial->setScaleEngine(new QwtLinearScaleEngine()); 169 | if (engine == "Log2") _dial->setScaleEngine(new QwtLogScaleEngine(2)); 170 | if (engine == "Log10") _dial->setScaleEngine(new QwtLogScaleEngine(10)); 171 | _dial->setScale(_dial->lowerBound(), _dial->upperBound()); //refresh 172 | } 173 | 174 | private slots: 175 | 176 | void handleSetTitle(const QString &title) 177 | { 178 | QGroupBox::setTitle(title); 179 | } 180 | 181 | protected: 182 | void mousePressEvent(QMouseEvent *event) 183 | { 184 | QGroupBox::mousePressEvent(event); 185 | event->ignore(); //allows for dragging from QGroupBox title 186 | } 187 | 188 | private: 189 | QwtDial *_dial; 190 | }; 191 | 192 | static Pothos::BlockRegistry registerQwtDialBlock( 193 | "/widgets/qwt_dial", &QwtDialBlock::make); 194 | 195 | #include "QwtDial.moc" 196 | -------------------------------------------------------------------------------- /QwtWidgets/QwtKnob.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2021 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | /*********************************************************************** 13 | * |PothosDoc QWT Knob 14 | * 15 | * A knob widget for angular control of a floating point value. 16 | * http://qwt.sourceforge.net/class_qwt_knob.html 17 | * 18 | * |category /Widgets 19 | * |keywords knob turn dial 20 | * 21 | * |param title The name of the value displayed by this widget 22 | * |default "My Knob" 23 | * |widget StringEntry() 24 | * 25 | * |param scaleEngine[Scale Engine] The scaling engine, linear or logarithmic. 26 | * |default "Linear" 27 | * |option [Linear] "Linear" 28 | * |option [Log2] "Log2" 29 | * |option [Log10] "Log10" 30 | * |preview disable 31 | * 32 | * |param value The initial value of this widget. 33 | * |default 0.0 34 | * |widget DoubleSpinBox() 35 | * 36 | * |param lowerBound[Lower Bound] The minimum value of this widget. 37 | * |default -1.0 38 | * |widget DoubleSpinBox() 39 | * 40 | * |param upperBound[Upper Bound] The maximum value of this widget. 41 | * |default 1.0 42 | * |widget DoubleSpinBox() 43 | * 44 | * |param stepSize[Step Size] The delta in-between discrete values in this widget. 45 | * Use a value of 0.0 for automatic step-size calculation. 46 | * |default 0.0 47 | * |widget DoubleSpinBox() 48 | * |preview valid 49 | * 50 | * |mode graphWidget 51 | * |factory /widgets/qwt_knob() 52 | * |setter setTitle(title) 53 | * |setter setScaleEngine(scaleEngine) 54 | * |setter setLowerBound(lowerBound) 55 | * |setter setUpperBound(upperBound) 56 | * |setter setStepSize(stepSize) 57 | * |setter setValue(value) 58 | **********************************************************************/ 59 | class QwtKnobBlock : public QGroupBox, public Pothos::Block 60 | { 61 | Q_OBJECT 62 | public: 63 | 64 | static Block *make(void) 65 | { 66 | return new QwtKnobBlock(); 67 | } 68 | 69 | QwtKnobBlock(void): 70 | _knob(new QwtKnob(this)) 71 | { 72 | auto layout = new QVBoxLayout(this); 73 | layout->setContentsMargins(QMargins()); 74 | layout->addWidget(_knob); 75 | this->setStyleSheet("QGroupBox {font-weight: bold;}"); 76 | 77 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtKnobBlock, setTitle)); 78 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtKnobBlock, widget)); 79 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtKnobBlock, value)); 80 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtKnobBlock, setValue)); 81 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtKnobBlock, setLowerBound)); 82 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtKnobBlock, setUpperBound)); 83 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtKnobBlock, setStepSize)); 84 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtKnobBlock, setScaleEngine)); 85 | this->registerSignal("valueChanged"); 86 | connect(_knob, &QwtKnob::valueChanged, this, &QwtKnobBlock::handleValueChanged); 87 | } 88 | 89 | QWidget *widget(void) 90 | { 91 | return this; 92 | } 93 | 94 | void setTitle(const QString &title) 95 | { 96 | QMetaObject::invokeMethod(this, "handleSetTitle", Qt::QueuedConnection, Q_ARG(QString, title)); 97 | } 98 | 99 | double value(void) const 100 | { 101 | return _knob->value(); 102 | } 103 | 104 | void setValue(const double value) 105 | { 106 | _knob->setValue(value); 107 | } 108 | 109 | void setLowerBound(const double value) 110 | { 111 | _knob->setLowerBound(value); 112 | } 113 | 114 | void setUpperBound(const double value) 115 | { 116 | _knob->setUpperBound(value); 117 | } 118 | 119 | void setStepSize(const double step) 120 | { 121 | _knob->setScaleStepSize(step); 122 | } 123 | 124 | void setScaleEngine(const QString &engine) 125 | { 126 | if (engine == "Linear") _knob->setScaleEngine(new QwtLinearScaleEngine()); 127 | if (engine == "Log2") _knob->setScaleEngine(new QwtLogScaleEngine(2)); 128 | if (engine == "Log10") _knob->setScaleEngine(new QwtLogScaleEngine(10)); 129 | _knob->setScale(_knob->lowerBound(), _knob->upperBound()); //refresh 130 | } 131 | 132 | void activate(void) 133 | { 134 | //emit current value when design becomes active 135 | this->emitSignal("valueChanged", this->value()); 136 | } 137 | 138 | public slots: 139 | 140 | QVariant saveState(void) const 141 | { 142 | return this->value(); 143 | } 144 | 145 | void restoreState(const QVariant &state) 146 | { 147 | this->setValue(state.toDouble()); 148 | } 149 | 150 | private slots: 151 | void handleValueChanged(const double value) 152 | { 153 | this->emitSignal("valueChanged", value); 154 | } 155 | 156 | void handleSetTitle(const QString &title) 157 | { 158 | QGroupBox::setTitle(title); 159 | } 160 | 161 | protected: 162 | void mousePressEvent(QMouseEvent *event) 163 | { 164 | QGroupBox::mousePressEvent(event); 165 | event->ignore(); //allows for dragging from QGroupBox title 166 | } 167 | 168 | private: 169 | QwtKnob *_knob; 170 | }; 171 | 172 | static Pothos::BlockRegistry registerQwtKnobBlock( 173 | "/widgets/qwt_knob", &QwtKnobBlock::make); 174 | 175 | #include "QwtKnob.moc" 176 | -------------------------------------------------------------------------------- /QwtWidgets/QwtSlider.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2021 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | /*********************************************************************** 13 | * |PothosDoc QWT Slider 14 | * 15 | * A slider widget for graphical control of a floating point value. 16 | * http://qwt.sourceforge.net/class_qwt_slider.html 17 | * 18 | * |category /Widgets 19 | * |keywords slider 20 | * 21 | * |param title The name of the value displayed by this widget 22 | * |default "My Slider" 23 | * |widget StringEntry() 24 | * 25 | * |param orientation The widget orientation (horizontal or vertical). 26 | * |default "Horizontal" 27 | * |option [Horizontal] "Horizontal" 28 | * |option [Vertical] "Vertical" 29 | * |preview disable 30 | * 31 | * |param scalePosition[Scale Position] The widget scale markers. 32 | * |default "NoScale" 33 | * |option [None] "NoScale" 34 | * |option [Leading] "LeadingScale" 35 | * |option [Trailing] "TrailingScale" 36 | * |preview disable 37 | * |tab Scale 38 | * 39 | * |param scaleEngine[Scale Engine] The scaling engine, linear or logarithmic. 40 | * |default "Linear" 41 | * |option [Linear] "Linear" 42 | * |option [Log2] "Log2" 43 | * |option [Log10] "Log10" 44 | * |preview disable 45 | * |tab Scale 46 | * 47 | * |param value The initial value of this widget. 48 | * |default 0.0 49 | * |widget DoubleSpinBox() 50 | * 51 | * |param lowerBound[Lower Bound] The minimum value of this widget. 52 | * |default -1.0 53 | * |widget DoubleSpinBox() 54 | * 55 | * |param upperBound[Upper Bound] The maximum value of this widget. 56 | * |default 1.0 57 | * |widget DoubleSpinBox() 58 | * 59 | * |param stepSize[Step Size] The delta in-between discrete values in this widget. 60 | * Use a value of 0.0 for automatic step-size calculation. 61 | * |default 0.0 62 | * |widget DoubleSpinBox() 63 | * |preview valid 64 | * 65 | * |mode graphWidget 66 | * |factory /widgets/qwt_slider() 67 | * |setter setTitle(title) 68 | * |setter setOrientation(orientation) 69 | * |setter setScalePosition(scalePosition) 70 | * |setter setScaleEngine(scaleEngine) 71 | * |setter setLowerBound(lowerBound) 72 | * |setter setUpperBound(upperBound) 73 | * |setter setStepSize(stepSize) 74 | * |setter setValue(value) 75 | **********************************************************************/ 76 | class QwtSliderBlock : public QGroupBox, public Pothos::Block 77 | { 78 | Q_OBJECT 79 | public: 80 | 81 | static Block *make(void) 82 | { 83 | return new QwtSliderBlock(); 84 | } 85 | 86 | QwtSliderBlock(void): 87 | _slider(new QwtSlider(this)) 88 | { 89 | auto layout = new QVBoxLayout(this); 90 | layout->setContentsMargins(QMargins()); 91 | layout->addWidget(_slider); 92 | this->setStyleSheet("QGroupBox {font-weight: bold;}"); 93 | 94 | _slider->setHandleSize(QSize(14, 14)); 95 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtSliderBlock, setTitle)); 96 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtSliderBlock, widget)); 97 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtSliderBlock, value)); 98 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtSliderBlock, setValue)); 99 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtSliderBlock, setLowerBound)); 100 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtSliderBlock, setUpperBound)); 101 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtSliderBlock, setStepSize)); 102 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtSliderBlock, setOrientation)); 103 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtSliderBlock, setScalePosition)); 104 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtSliderBlock, setScaleEngine)); 105 | this->registerSignal("valueChanged"); 106 | connect(_slider, &QwtSlider::valueChanged, this, &QwtSliderBlock::handleValueChanged); 107 | } 108 | 109 | QWidget *widget(void) 110 | { 111 | return this; 112 | } 113 | 114 | void setTitle(const QString &title) 115 | { 116 | QMetaObject::invokeMethod(this, "handleSetTitle", Qt::QueuedConnection, Q_ARG(QString, title)); 117 | } 118 | 119 | double value(void) const 120 | { 121 | return _slider->value(); 122 | } 123 | 124 | void setValue(const double value) 125 | { 126 | _slider->setValue(value); 127 | } 128 | 129 | void setLowerBound(const double value) 130 | { 131 | _slider->setLowerBound(value); 132 | } 133 | 134 | void setUpperBound(const double value) 135 | { 136 | _slider->setUpperBound(value); 137 | } 138 | 139 | void setOrientation(const QString &orientation) 140 | { 141 | if ((orientation == "Horizontal")) _slider->setOrientation(Qt::Horizontal); 142 | if ((orientation == "Vertical")) _slider->setOrientation(Qt::Vertical); 143 | } 144 | 145 | void setScalePosition(const QString &scale) 146 | { 147 | if ((scale == "NoScale")) _slider->setScalePosition(_slider->NoScale); 148 | if ((scale == "LeadingScale")) _slider->setScalePosition(_slider->LeadingScale); 149 | if ((scale == "TrailingScale")) _slider->setScalePosition(_slider->TrailingScale); 150 | } 151 | 152 | void setScaleEngine(const QString &engine) 153 | { 154 | if (engine == "Linear") _slider->setScaleEngine(new QwtLinearScaleEngine()); 155 | if (engine == "Log2") _slider->setScaleEngine(new QwtLogScaleEngine(2)); 156 | if (engine == "Log10") _slider->setScaleEngine(new QwtLogScaleEngine(10)); 157 | _slider->setScale(_slider->lowerBound(), _slider->upperBound()); //refresh 158 | } 159 | 160 | void setStepSize(const double step) 161 | { 162 | _slider->setScaleStepSize(step); 163 | } 164 | 165 | void activate(void) 166 | { 167 | //emit current value when design becomes active 168 | this->emitSignal("valueChanged", this->value()); 169 | } 170 | 171 | public slots: 172 | 173 | QVariant saveState(void) const 174 | { 175 | return this->value(); 176 | } 177 | 178 | void restoreState(const QVariant &state) 179 | { 180 | this->setValue(state.toDouble()); 181 | } 182 | 183 | private slots: 184 | void handleValueChanged(const double value) 185 | { 186 | this->emitSignal("valueChanged", value); 187 | } 188 | 189 | void handleSetTitle(const QString &title) 190 | { 191 | QGroupBox::setTitle(title); 192 | } 193 | 194 | protected: 195 | void mousePressEvent(QMouseEvent *event) 196 | { 197 | QGroupBox::mousePressEvent(event); 198 | event->ignore(); //allows for dragging from QGroupBox title 199 | } 200 | 201 | private: 202 | QwtSlider *_slider; 203 | }; 204 | 205 | static Pothos::BlockRegistry registerQwtSliderBlock( 206 | "/widgets/qwt_slider", &QwtSliderBlock::make); 207 | 208 | #include "QwtSlider.moc" 209 | -------------------------------------------------------------------------------- /QwtWidgets/QwtThermo.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2017 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | /*********************************************************************** 12 | * |PothosDoc QWT Thermo 13 | * 14 | * A thermometer-like status indicator for a floating point value. 15 | * http://qwt.sourceforge.net/class_qwt_thermo.html 16 | * 17 | * |category /Widgets 18 | * |keywords thermo indicator status 19 | * 20 | * |param title The name of the value displayed by this widget 21 | * |default "My Thermo" 22 | * |widget StringEntry() 23 | * 24 | * |param orientation The widget orientation (horizontal or vertical). 25 | * |default "Horizontal" 26 | * |option [Horizontal] "Horizontal" 27 | * |option [Vertical] "Vertical" 28 | * |preview disable 29 | * 30 | * |param scalePosition[Scale Position] The widget scale markers. 31 | * |default "NoScale" 32 | * |option [None] "NoScale" 33 | * |option [Leading] "LeadingScale" 34 | * |option [Trailing] "TrailingScale" 35 | * |preview disable 36 | * |tab Scale 37 | * 38 | * |param scaleEngine[Scale Engine] The scaling engine, linear or logarithmic. 39 | * |default "Linear" 40 | * |option [Linear] "Linear" 41 | * |option [Log2] "Log2" 42 | * |option [Log10] "Log10" 43 | * |preview disable 44 | * |tab Scale 45 | * 46 | * |param lowerBound[Lower Bound] The minimum value of this widget. 47 | * |default -1.0 48 | * |widget DoubleSpinBox() 49 | * 50 | * |param upperBound[Upper Bound] The maximum value of this widget. 51 | * |default 1.0 52 | * |widget DoubleSpinBox() 53 | * 54 | * |param stepSize[Step Size] The delta in-between discrete values in this widget. 55 | * Use a value of 0.0 for automatic step-size calculation. 56 | * |default 0.0 57 | * |widget DoubleSpinBox() 58 | * |preview valid 59 | * 60 | * |param fillColor[Fill Color] The color of the liquid in the thermometer display. 61 | * |widget ColorPicker(mode=pastel) 62 | * |default "#b19cd9" 63 | * |preview disable 64 | * 65 | * |param alarmEnabled[Alarm Enabled] True to enable the alarm state coloring. 66 | * |default false 67 | * |option [Enabled] true 68 | * |option [Disabled] false 69 | * |tab Alarm 70 | * |preview disable 71 | * 72 | * |param alarmLevel[Alarm Level] The threshold for entering the alarm state. 73 | * |default 0.5 74 | * |widget DoubleSpinBox() 75 | * |tab Alarm 76 | * |preview when(enum=alarmEnabled, true) 77 | * 78 | * |param alarmColor[Alarm Color] The color of the liquid in the thermometer display in alarm mode. 79 | * |widget ColorPicker(mode=pastel) 80 | * |default "#ff6961" 81 | * |tab Alarm 82 | * |preview disable 83 | * 84 | * |mode graphWidget 85 | * |factory /widgets/qwt_thermo() 86 | * |setter setTitle(title) 87 | * |setter setOrientation(orientation) 88 | * |setter setScalePosition(scalePosition) 89 | * |setter setScaleEngine(scaleEngine) 90 | * |setter setLowerBound(lowerBound) 91 | * |setter setUpperBound(upperBound) 92 | * |setter setStepSize(stepSize) 93 | * |setter setFillColor(fillColor) 94 | * |setter setAlarmEnabled(alarmEnabled) 95 | * |setter setAlarmLevel(alarmLevel) 96 | * |setter setAlarmColor(alarmColor) 97 | **********************************************************************/ 98 | class QwtThermoBlock : public QGroupBox, public Pothos::Block 99 | { 100 | Q_OBJECT 101 | public: 102 | 103 | static Block *make(void) 104 | { 105 | return new QwtThermoBlock(); 106 | } 107 | 108 | QwtThermoBlock(void): 109 | _thermo(new QwtThermo(this)) 110 | { 111 | auto layout = new QVBoxLayout(this); 112 | layout->setContentsMargins(QMargins()); 113 | layout->addWidget(_thermo); 114 | this->setStyleSheet("QGroupBox {font-weight: bold;}"); 115 | 116 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtThermoBlock, setTitle)); 117 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtThermoBlock, widget)); 118 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtThermoBlock, value)); 119 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtThermoBlock, setValue)); 120 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtThermoBlock, setLowerBound)); 121 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtThermoBlock, setUpperBound)); 122 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtThermoBlock, setStepSize)); 123 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtThermoBlock, setOrientation)); 124 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtThermoBlock, setScalePosition)); 125 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtThermoBlock, setScaleEngine)); 126 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtThermoBlock, setFillColor)); 127 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtThermoBlock, setAlarmEnabled)); 128 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtThermoBlock, setAlarmLevel)); 129 | this->registerCall(this, POTHOS_FCN_TUPLE(QwtThermoBlock, setAlarmColor)); 130 | } 131 | 132 | QWidget *widget(void) 133 | { 134 | return this; 135 | } 136 | 137 | void setTitle(const QString &title) 138 | { 139 | QMetaObject::invokeMethod(this, "handleSetTitle", Qt::QueuedConnection, Q_ARG(QString, title)); 140 | } 141 | 142 | double value(void) const 143 | { 144 | return _thermo->value(); 145 | } 146 | 147 | void setValue(const double value) 148 | { 149 | _thermo->setValue(value); 150 | } 151 | 152 | void setLowerBound(const double value) 153 | { 154 | _thermo->setLowerBound(value); 155 | } 156 | 157 | void setUpperBound(const double value) 158 | { 159 | _thermo->setUpperBound(value); 160 | } 161 | 162 | void setAlarmEnabled(const bool enb) 163 | { 164 | _thermo->setAlarmEnabled(enb); 165 | } 166 | 167 | void setAlarmLevel(const double level) 168 | { 169 | const bool enb = _thermo->alarmEnabled(); //save enabled 170 | _thermo->setAlarmLevel(level); 171 | _thermo->setAlarmEnabled(enb); //restore after setAlarmLevel 172 | } 173 | 174 | void setOrientation(const QString &orientation) 175 | { 176 | if ((orientation == "Horizontal")) _thermo->setOrientation(Qt::Horizontal); 177 | if ((orientation == "Vertical")) _thermo->setOrientation(Qt::Vertical); 178 | } 179 | 180 | void setScalePosition(const QString &scale) 181 | { 182 | if ((scale == "NoScale")) _thermo->setScalePosition(_thermo->NoScale); 183 | if ((scale == "LeadingScale")) _thermo->setScalePosition(_thermo->LeadingScale); 184 | if ((scale == "TrailingScale")) _thermo->setScalePosition(_thermo->TrailingScale); 185 | } 186 | 187 | void setScaleEngine(const QString &engine) 188 | { 189 | if (engine == "Linear") _thermo->setScaleEngine(new QwtLinearScaleEngine()); 190 | if (engine == "Log2") _thermo->setScaleEngine(new QwtLogScaleEngine(2)); 191 | if (engine == "Log10") _thermo->setScaleEngine(new QwtLogScaleEngine(10)); 192 | _thermo->setScale(_thermo->lowerBound(), _thermo->upperBound()); //refresh 193 | } 194 | 195 | void setStepSize(const double step) 196 | { 197 | _thermo->setScaleStepSize(step); 198 | } 199 | 200 | void setFillColor(const QString &color) 201 | { 202 | _thermo->setFillBrush(QBrush(QColor(color))); 203 | } 204 | 205 | void setAlarmColor(const QString &color) 206 | { 207 | _thermo->setAlarmBrush(QBrush(QColor(color))); 208 | } 209 | 210 | private slots: 211 | 212 | void handleSetTitle(const QString &title) 213 | { 214 | QGroupBox::setTitle(title); 215 | } 216 | 217 | protected: 218 | void mousePressEvent(QMouseEvent *event) 219 | { 220 | QGroupBox::mousePressEvent(event); 221 | event->ignore(); //allows for dragging from QGroupBox title 222 | } 223 | 224 | private: 225 | QwtThermo *_thermo; 226 | }; 227 | 228 | static Pothos::BlockRegistry registerQwtThermoBlock( 229 | "/widgets/qwt_thermo", &QwtThermoBlock::make); 230 | 231 | #include "QwtThermo.moc" 232 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Plotters for visualization in Pothos GUI 2 | 3 | The plotters toolkit provides plotter widgets which are combined 4 | processing blocks with Qt widgets which are capable of 5 | displaying signal visualization within the Pothos GUI. 6 | Such blocks include: time-domain waveform viewer, periodogram, 7 | spectrogram, constellation plotter, and others 8 | 9 | ## Documentation 10 | 11 | * https://github.com/pothosware/PothosPlotters/wiki 12 | 13 | ## Dependencies 14 | 15 | * Pothos library 16 | * Pothos communications toolkit 17 | * Qwt - http://qwt.sourceforge.net/ 18 | 19 | ## Licensing information 20 | 21 | Use, modification and distribution is subject to the Boost Software 22 | License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at 23 | http://www.boost.org/LICENSE_1_0.txt) 24 | -------------------------------------------------------------------------------- /Spectrogram/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ######################################################################## 2 | ## Feature registration 3 | ######################################################################## 4 | cmake_dependent_option(ENABLE_PLOTTERS_SPECTROGRAM "Enable Pothos Plotters.Spectrogram component" ON "ENABLE_PLOTTERS;Spuce_FOUND" OFF) 5 | add_feature_info(" Spectrogram" ENABLE_PLOTTERS_SPECTROGRAM "Spectrogram frequency-domain FFT plotter") 6 | if (NOT ENABLE_PLOTTERS_SPECTROGRAM) 7 | return() 8 | endif() 9 | 10 | ######################################################################## 11 | # Build spectrogram plot module 12 | ######################################################################## 13 | include_directories(${Spuce_INCLUDE_DIRS}) 14 | 15 | POTHOS_MODULE_UTIL( 16 | TARGET Spectrogram 17 | SOURCES 18 | Spectrogram.cpp 19 | SpectrogramWork.cpp 20 | SpectrogramDisplay.cpp 21 | ColorMapEntry.cpp 22 | GeneratedColorMaps.cpp 23 | QwtColorMapMaker.cpp 24 | DOC_SOURCES Spectrogram.cpp 25 | LIBRARIES 26 | Qt${QT_VERSION_MAJOR}::Widgets 27 | PothosQwt 28 | PothosPlotterUtils 29 | spuce 30 | DESTINATION plotters 31 | ) 32 | -------------------------------------------------------------------------------- /Spectrogram/ColorMapEntry.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2021 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "GeneratedColorMaps.hpp" 14 | 15 | /*********************************************************************** 16 | * ComboBox for drop-down entry 17 | **********************************************************************/ 18 | class ColorMapEntry : public QComboBox 19 | { 20 | Q_OBJECT 21 | public: 22 | ColorMapEntry(QWidget *parent): 23 | QComboBox(parent) 24 | { 25 | connect(this, QOverload::of(&QComboBox::currentIndexChanged), [=](int){emit this->widgetChanged();}); 26 | connect(this, &QComboBox::editTextChanged, [=](const QString &){emit this->entryChanged();}); 27 | this->view()->setObjectName("BlockPropertiesEditWidget"); //to pick up eval color style 28 | } 29 | 30 | public slots: 31 | QString value(void) const 32 | { 33 | const auto index = QComboBox::currentIndex(); 34 | if (index < 0 or QComboBox::currentText() != QComboBox::itemText(index)) return QComboBox::currentText(); 35 | else return QComboBox::itemData(index).toString(); 36 | } 37 | 38 | void setValue(const QString &value) 39 | { 40 | int index = -1; 41 | for (int i = 0; i < QComboBox::count(); i++) 42 | { 43 | if (QComboBox::itemData(i).toString() == value) index = i; 44 | } 45 | if (index < 0) QComboBox::setEditText(value); 46 | else QComboBox::setCurrentIndex(index); 47 | } 48 | 49 | signals: 50 | void commitRequested(void); 51 | void widgetChanged(void); 52 | void entryChanged(void); 53 | }; 54 | 55 | /*********************************************************************** 56 | * Helpful icons to preview color map 57 | **********************************************************************/ 58 | static QIcon makeColorMapIcon(const std::string &name) 59 | { 60 | std::unique_ptr colorMap(makeQwtColorMap(name)); 61 | QPixmap pixmap(100, 20); 62 | QPainter painter(&pixmap); 63 | painter.setPen(Qt::transparent); 64 | const QwtInterval interval(0.0, double(pixmap.width())); 65 | for (int i = 0; i < pixmap.width(); i++) 66 | { 67 | painter.setBrush(QColor(colorMap->rgb(interval, double(i)))); 68 | painter.drawRect(QRectF(qreal(i), qreal(0), qreal(1), pixmap.height())); 69 | } 70 | return QIcon(pixmap); 71 | } 72 | 73 | /*********************************************************************** 74 | * Factory function and registration 75 | **********************************************************************/ 76 | static QWidget *makeColorMapEntry(const QJsonArray &, const QJsonObject &, QWidget *parent) 77 | { 78 | auto colorMapEntry = new ColorMapEntry(parent); 79 | colorMapEntry->setIconSize(QSize(100, 20)); 80 | for (const auto &pair : availableColorMaps()) 81 | { 82 | colorMapEntry->addItem( 83 | makeColorMapIcon(pair.second), 84 | QString::fromStdString(pair.first), 85 | QString("\"%1\"").arg(QString::fromStdString(pair.second))); 86 | } 87 | return colorMapEntry; 88 | } 89 | 90 | pothos_static_block(registerColorMapEntry) 91 | { 92 | Pothos::PluginRegistry::add("/flow/EntryWidgets/ColorMapEntry", Pothos::Callable(&makeColorMapEntry)); 93 | } 94 | 95 | #include "ColorMapEntry.moc" 96 | -------------------------------------------------------------------------------- /Spectrogram/GeneratedColorMaps.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2016 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class QwtColorMap; 10 | 11 | //! Get a listing of all title, name pairs for each color map 12 | std::vector> availableColorMaps(void); 13 | 14 | //! Get a color mapping given a name in [Z, R, G, B, A] format 15 | std::vector> lookupColorMap(const std::string &name); 16 | 17 | //! Make a QwtColorMap given the name of a mapping 18 | QwtColorMap *makeQwtColorMap(const std::string &name); 19 | -------------------------------------------------------------------------------- /Spectrogram/QwtColorMapMaker.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2016 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include 5 | #include "GeneratedColorMaps.hpp" 6 | #include 7 | 8 | static QColor vecToColor(const std::vector &vec) 9 | { 10 | std::vector colorsF(vec); 11 | colorsF.resize(5, 1.0); //last 4 entries are r, g, b, a with 1.0 default 12 | return QColor::fromRgbF(colorsF[1], colorsF[2], colorsF[3], colorsF[4]); 13 | } 14 | 15 | QwtColorMap *makeQwtColorMap(const std::string &name) 16 | { 17 | const auto colorMapData = lookupColorMap(name); 18 | if (colorMapData.size() < 2) throw Pothos::InvalidArgumentException("color map lookup failed: "+name); 19 | auto cMap = new QwtLinearColorMap(vecToColor(colorMapData.front()), vecToColor(colorMapData.back())); 20 | for (size_t i = 1; i < colorMapData.size()-1; i++) 21 | { 22 | const auto &vec = colorMapData.at(i); 23 | cMap->addColorStop(vec.at(0), vecToColor(vec)); 24 | } 25 | return cMap; 26 | } 27 | -------------------------------------------------------------------------------- /Spectrogram/Spectrogram.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2016 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include "SpectrogramDisplay.hpp" 5 | #include 6 | #include 7 | #include 8 | 9 | /*********************************************************************** 10 | * |PothosDoc Spectrogram 11 | * 12 | * The spectrogram plot displays a live plot of the spectrum vs time. 13 | * 14 | * |category /Plotters 15 | * |keywords frequency plot fft dft spectrum spectral 16 | * |alias /widgets/spectrogram 17 | * 18 | * |param title The title of the plot 19 | * |default "Spectrogram" 20 | * |widget StringEntry() 21 | * |preview valid 22 | * 23 | * |param displayRate[Display Rate] How often the plotter updates. 24 | * |default 10.0 25 | * |units updates/sec 26 | * |preview disable 27 | * 28 | * |param sampleRate[Sample Rate] The rate of the input elements. 29 | * |default 1e6 30 | * |units samples/sec 31 | * 32 | * |param centerFreq[Center Freq] The center frequency of the plot. 33 | * This value controls the labeling of the horizontal access. 34 | * |default 0.0 35 | * |units Hz 36 | * |preview valid 37 | * 38 | * |param numBins[Num FFT Bins] The number of bins per fourier transform. 39 | * |default 1024 40 | * |option 512 41 | * |option 1024 42 | * |option 2048 43 | * |option 4096 44 | * |widget ComboBox(editable=true) 45 | * |preview disable 46 | * |tab FFT 47 | * 48 | * |param window[Window Type] The window function controls passband ripple. 49 | * |default "hann" 50 | * |option [Rectangular] "rectangular" 51 | * |option [Hann] "hann" 52 | * |option [Hamming] "hamming" 53 | * |option [Blackman] "blackman" 54 | * |option [Bartlett] "bartlett" 55 | * |option [Flat-top] "flattop" 56 | * |option [Kaiser] "kaiser" 57 | * |option [Chebyshev] "chebyshev" 58 | * |preview disable 59 | * |tab FFT 60 | * 61 | * |param windowArgs[Window Args] Optional window arguments (depends on window type). 62 | *
    63 | *
  • When using the Kaiser window, specify [beta] to use the parameterized Kaiser window.
  • 64 | *
  • When using the Chebyshev window, specify [atten] to use the Dolph-Chebyshev window with attenuation in dB.
  • 65 | *
66 | * |default [] 67 | * |preview disable 68 | * |tab FFT 69 | * 70 | * |param fullScale[Full Scale] The amplitude that corresponds to full-scale. 71 | * A full-scale amplitude signal will appear as 0.0 dBfs on the plotter. 72 | * The default value of 1.0 works best for scaled floating point samples. 73 | * A signed 16-bit integer value might use 32768 as full-scale instead. 74 | * |default 1.0 75 | * |preview disable 76 | * |tab FFT 77 | * 78 | * |param fftMode[FFT Mode] Power spectrum display mode. 79 | *
    80 | *
  • Real mode ("REAL") displays only the positive frequencies between [0, +fs/2].
  • 81 | *
  • Complex mode ("COMPLEX) displays positive and negative frequencies between [-fs/2, +fs/2].
  • 82 | *
  • Automatic mode ("AUTO") selects the FFT mode based on the data type of the current signal.
  • 83 | *
84 | * |default "AUTO" 85 | * |option [Automatic] "AUTO" 86 | * |option [Complex] "COMPLEX" 87 | * |option [Real] "REAL" 88 | * |preview disable 89 | * |tab FFT 90 | * 91 | * |param timeSpan[Time Span] How many seconds of data to display in the plot. 92 | * |default 10.0 93 | * |units seconds 94 | * |preview disable 95 | * |tab Axis 96 | * 97 | * |param refLevel[Reference Level] The maximum displayable power level. 98 | * |default 0.0 99 | * |units dBxx 100 | * |widget DoubleSpinBox(minimum=-150, maximum=150, step=10, decimals=1) 101 | * |preview disable 102 | * |tab Axis 103 | * 104 | * |param dynRange[Dynamic Range] The ratio of largest to smallest displayable power level. 105 | * The vertical axis will display values from the ref level to ref level - dynamic range. 106 | * |default 100.0 107 | * |units dB 108 | * |widget DoubleSpinBox(minimum=10, maximum=200, step=10, decimals=1) 109 | * |preview disable 110 | * |tab Axis 111 | * 112 | * |param enableXAxis[Enable X-Axis] Show or hide the horizontal axis markers. 113 | * |option [Show] true 114 | * |option [Hide] false 115 | * |default true 116 | * |preview disable 117 | * |tab Axis 118 | * 119 | * |param enableYAxis[Enable Y-Axis] Show or hide the vertical axis markers. 120 | * |option [Show] true 121 | * |option [Hide] false 122 | * |default true 123 | * |preview disable 124 | * |tab Axis 125 | * 126 | * |param colorMap[Color Map] The name of a color map for the raster plot. 127 | * |widget ColorMapEntry() 128 | * |default "rainbow" 129 | * |preview disable 130 | * |tab Axis 131 | * 132 | * |param freqLabelId[Freq Label ID] Labels with this ID can be used to set the center frequency. 133 | * To ignore frequency labels, set this parameter to an empty string. 134 | * |default "rxFreq" 135 | * |widget StringEntry() 136 | * |preview disable 137 | * |tab Labels 138 | * 139 | * |param rateLabelId[Rate Label ID] Labels with this ID can be used to set the sample rate. 140 | * To ignore sample rate labels, set this parameter to an empty string. 141 | * |default "rxRate" 142 | * |widget StringEntry() 143 | * |preview disable 144 | * |tab Labels 145 | * 146 | * |param startLabelId[Start Label ID] Align captured input to the specified label ID. 147 | * An empty label ID disables this feature. 148 | * |default "" 149 | * |widget StringEntry() 150 | * |preview disable 151 | * |tab Labels 152 | * 153 | * |mode graphWidget 154 | * |factory /plotters/spectrogram(remoteEnv) 155 | * |setter setTitle(title) 156 | * |setter setDisplayRate(displayRate) 157 | * |setter setSampleRate(sampleRate) 158 | * |setter setCenterFrequency(centerFreq) 159 | * |setter setNumFFTBins(numBins) 160 | * |setter setWindowType(window, windowArgs) 161 | * |setter setFullScale(fullScale) 162 | * |setter setFFTMode(fftMode) 163 | * |setter setTimeSpan(timeSpan) 164 | * |setter setReferenceLevel(refLevel) 165 | * |setter setDynamicRange(dynRange) 166 | * |setter enableXAxis(enableXAxis) 167 | * |setter enableYAxis(enableYAxis) 168 | * |setter setColorMap(colorMap) 169 | * |setter setFreqLabelId(freqLabelId) 170 | * |setter setRateLabelId(rateLabelId) 171 | * |setter setStartLabelId(startLabelId) 172 | **********************************************************************/ 173 | class Spectrogram : public Pothos::Topology 174 | { 175 | public: 176 | static Topology *make(const Pothos::ProxyEnvironment::Sptr &remoteEnv) 177 | { 178 | return new Spectrogram(remoteEnv); 179 | } 180 | 181 | Spectrogram(const Pothos::ProxyEnvironment::Sptr &remoteEnv) 182 | { 183 | _display.reset(new SpectrogramDisplay()); 184 | _display->setName("Display"); 185 | 186 | auto registry = remoteEnv->findProxy("Pothos/BlockRegistry"); 187 | _trigger = registry.call("/comms/wave_trigger"); 188 | _trigger.call("setName", "Trigger"); 189 | _trigger.call("setMode", "PERIODIC"); 190 | 191 | //register calls in this topology 192 | this->registerCall(this, POTHOS_FCN_TUPLE(Spectrogram, setNumFFTBins)); 193 | this->registerCall(this, POTHOS_FCN_TUPLE(Spectrogram, setFreqLabelId)); 194 | this->registerCall(this, POTHOS_FCN_TUPLE(Spectrogram, setRateLabelId)); 195 | this->registerCall(this, POTHOS_FCN_TUPLE(Spectrogram, setStartLabelId)); 196 | 197 | //connect to internal display block 198 | this->connect(this, "setTitle", _display, "setTitle"); 199 | this->connect(this, "setDisplayRate", _display, "setDisplayRate"); 200 | this->connect(this, "setSampleRate", _display, "setSampleRate"); 201 | this->connect(this, "setCenterFrequency", _display, "setCenterFrequency"); 202 | this->connect(this, "setNumFFTBins", _display, "setNumFFTBins"); 203 | this->connect(this, "setWindowType", _display, "setWindowType"); 204 | this->connect(this, "setFullScale", _display, "setFullScale"); 205 | this->connect(this, "setTimeSpan", _display, "setTimeSpan"); 206 | this->connect(this, "setReferenceLevel", _display, "setReferenceLevel"); 207 | this->connect(this, "setDynamicRange", _display, "setDynamicRange"); 208 | this->connect(this, "enableXAxis", _display, "enableXAxis"); 209 | this->connect(this, "enableYAxis", _display, "enableYAxis"); 210 | this->connect(this, "setColorMap", _display, "setColorMap"); 211 | this->connect(_display, "frequencySelected", this, "frequencySelected"); 212 | this->connect(_display, "relativeFrequencySelected", this, "relativeFrequencySelected"); 213 | 214 | //connect to the internal snooper block 215 | this->connect(_display, "updateRateChanged", _trigger, "setEventRate"); 216 | this->connect(this, "setNumFFTBins", _trigger, "setNumPoints"); 217 | 218 | //connect stream ports 219 | this->connect(this, 0, _trigger, 0); 220 | this->connect(_trigger, 0, _display, 0); 221 | } 222 | 223 | Pothos::Object opaqueCallMethod(const std::string &name, const Pothos::Object *inputArgs, const size_t numArgs) const 224 | { 225 | //calls that go to the topology 226 | try 227 | { 228 | return Pothos::Topology::opaqueCallMethod(name, inputArgs, numArgs); 229 | } 230 | catch (const Pothos::BlockCallNotFound &){} 231 | 232 | //forward everything else to display 233 | return _display->opaqueCallMethod(name, inputArgs, numArgs); 234 | } 235 | 236 | void setNumFFTBins(const size_t num) 237 | { 238 | _trigger.call("setNumPoints", num); 239 | _display->setNumFFTBins(num); 240 | } 241 | 242 | void setFreqLabelId(const std::string &id) 243 | { 244 | _display->setFreqLabelId(id); 245 | _freqLabelId = id; 246 | this->updateIdsList(); 247 | } 248 | 249 | void setRateLabelId(const std::string &id) 250 | { 251 | _display->setRateLabelId(id); 252 | _rateLabelId = id; 253 | this->updateIdsList(); 254 | } 255 | 256 | void setStartLabelId(const std::string &id) 257 | { 258 | _trigger.call("setLabelId", id); 259 | _trigger.call("setMode", id.empty()?"PERIODIC":"NORMAL"); 260 | } 261 | 262 | void updateIdsList(void) 263 | { 264 | std::vector ids; 265 | if (not _freqLabelId.empty()) ids.push_back(_freqLabelId); 266 | if (not _rateLabelId.empty()) ids.push_back(_rateLabelId); 267 | _trigger.call("setIdsList", ids); 268 | } 269 | 270 | private: 271 | Pothos::Proxy _trigger; 272 | std::shared_ptr _display; 273 | std::string _freqLabelId, _rateLabelId; 274 | }; 275 | 276 | /*********************************************************************** 277 | * registration 278 | **********************************************************************/ 279 | static Pothos::BlockRegistry registerSpectrogram( 280 | "/plotters/spectrogram", &Spectrogram::make); 281 | 282 | static Pothos::BlockRegistry registerSpectrogramOldPath( 283 | "/widgets/spectrogram", &Spectrogram::make); 284 | -------------------------------------------------------------------------------- /Spectrogram/SpectrogramDisplay.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2021 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include "GeneratedColorMaps.hpp" 5 | #include "SpectrogramDisplay.hpp" 6 | #include "PothosPlotPicker.hpp" 7 | #include "PothosPlotter.hpp" 8 | #include "SpectrogramRaster.hpp" 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include //min/max 21 | 22 | SpectrogramDisplay::SpectrogramDisplay(void): 23 | _replotTimer(new QTimer(this)), 24 | _mainPlot(new PothosPlotter(this, POTHOS_PLOTTER_ZOOM)), 25 | _plotSpect(new QwtPlotSpectrogram()), 26 | _plotRaster(new MySpectrogramRasterData()), 27 | _lastUpdateRate(1.0), 28 | _displayRate(1.0), 29 | _sampleRate(1.0), 30 | _sampleRateWoAxisUnits(1.0), 31 | _centerFreq(0.0), 32 | _centerFreqWoAxisUnits(0.0), 33 | _numBins(1024), 34 | _timeSpan(10.0), 35 | _refLevel(0.0), 36 | _dynRange(100.0), 37 | _fullScale(1.0), 38 | _fftModeComplex(true), 39 | _fftModeAutomatic(true), 40 | _freqLabelId("rxFreq"), 41 | _rateLabelId("rxRate") 42 | { 43 | //setup block 44 | this->registerCall(this, POTHOS_FCN_TUPLE(SpectrogramDisplay, widget)); 45 | this->registerCall(this, POTHOS_FCN_TUPLE(SpectrogramDisplay, setTitle)); 46 | this->registerCall(this, POTHOS_FCN_TUPLE(SpectrogramDisplay, setDisplayRate)); 47 | this->registerCall(this, POTHOS_FCN_TUPLE(SpectrogramDisplay, setSampleRate)); 48 | this->registerCall(this, POTHOS_FCN_TUPLE(SpectrogramDisplay, setCenterFrequency)); 49 | this->registerCall(this, POTHOS_FCN_TUPLE(SpectrogramDisplay, setNumFFTBins)); 50 | this->registerCall(this, POTHOS_FCN_TUPLE(SpectrogramDisplay, setWindowType)); 51 | this->registerCall(this, POTHOS_FCN_TUPLE(SpectrogramDisplay, setFullScale)); 52 | this->registerCall(this, POTHOS_FCN_TUPLE(SpectrogramDisplay, setFFTMode)); 53 | this->registerCall(this, POTHOS_FCN_TUPLE(SpectrogramDisplay, setTimeSpan)); 54 | this->registerCall(this, POTHOS_FCN_TUPLE(SpectrogramDisplay, setReferenceLevel)); 55 | this->registerCall(this, POTHOS_FCN_TUPLE(SpectrogramDisplay, setDynamicRange)); 56 | this->registerCall(this, POTHOS_FCN_TUPLE(SpectrogramDisplay, title)); 57 | this->registerCall(this, POTHOS_FCN_TUPLE(SpectrogramDisplay, displayRate)); 58 | this->registerCall(this, POTHOS_FCN_TUPLE(SpectrogramDisplay, sampleRate)); 59 | this->registerCall(this, POTHOS_FCN_TUPLE(SpectrogramDisplay, centerFrequency)); 60 | this->registerCall(this, POTHOS_FCN_TUPLE(SpectrogramDisplay, numFFTBins)); 61 | this->registerCall(this, POTHOS_FCN_TUPLE(SpectrogramDisplay, timeSpan)); 62 | this->registerCall(this, POTHOS_FCN_TUPLE(SpectrogramDisplay, referenceLevel)); 63 | this->registerCall(this, POTHOS_FCN_TUPLE(SpectrogramDisplay, dynamicRange)); 64 | this->registerCall(this, POTHOS_FCN_TUPLE(SpectrogramDisplay, enableXAxis)); 65 | this->registerCall(this, POTHOS_FCN_TUPLE(SpectrogramDisplay, enableYAxis)); 66 | this->registerCall(this, POTHOS_FCN_TUPLE(SpectrogramDisplay, setColorMap)); 67 | this->registerCall(this, POTHOS_FCN_TUPLE(SpectrogramDisplay, setFreqLabelId)); 68 | this->registerCall(this, POTHOS_FCN_TUPLE(SpectrogramDisplay, setRateLabelId)); 69 | this->registerSignal("frequencySelected"); 70 | this->registerSignal("relativeFrequencySelected"); 71 | this->registerSignal("updateRateChanged"); 72 | this->setupInput(0); 73 | 74 | //layout 75 | auto layout = new QHBoxLayout(this); 76 | layout->setSpacing(0); 77 | layout->setContentsMargins(QMargins()); 78 | layout->addWidget(_mainPlot); 79 | 80 | //setup plotter 81 | { 82 | qobject_cast(_mainPlot->zoomer())->registerRaster(_plotRaster); 83 | connect(_mainPlot->zoomer(), QOverload::of(&QwtPlotZoomer::selected), this, &SpectrogramDisplay::handlePickerSelected); 84 | connect(_mainPlot->zoomer(), &QwtPlotZoomer::zoomed, this, &SpectrogramDisplay::handleZoomed); 85 | _mainPlot->setAxisTitle(QwtPlot::yRight, "dB"); 86 | _mainPlot->plotLayout()->setAlignCanvasToScales(true); 87 | _mainPlot->enableAxis(QwtPlot::yRight); 88 | _mainPlot->axisWidget(QwtPlot::yRight)->setColorBarEnabled(true); 89 | } 90 | 91 | //setup spectrogram plot item 92 | { 93 | _plotSpect->attach(_mainPlot); 94 | _plotSpect->setData(_plotRaster); 95 | _plotSpect->setDisplayMode(QwtPlotSpectrogram::ImageMode, true); 96 | _plotSpect->setRenderThreadCount(0); //enable multi-thread 97 | } 98 | 99 | connect(_replotTimer, &QTimer::timeout, _mainPlot, &PothosPlotter::replot); 100 | } 101 | 102 | SpectrogramDisplay::~SpectrogramDisplay(void) 103 | { 104 | return; 105 | } 106 | 107 | void SpectrogramDisplay::setTitle(const QString &title) 108 | { 109 | QMetaObject::invokeMethod(_mainPlot, "setTitle", Qt::QueuedConnection, Q_ARG(QString, title)); 110 | } 111 | 112 | void SpectrogramDisplay::setDisplayRate(const double displayRate) 113 | { 114 | _displayRate = displayRate; 115 | _replotTimer->setInterval(int(1000/_displayRate)); 116 | } 117 | 118 | void SpectrogramDisplay::setSampleRate(const double sampleRate) 119 | { 120 | _sampleRate = sampleRate; 121 | QMetaObject::invokeMethod(this, "handleUpdateAxis", Qt::QueuedConnection); 122 | } 123 | 124 | void SpectrogramDisplay::setCenterFrequency(const double freq) 125 | { 126 | _centerFreq = freq; 127 | QMetaObject::invokeMethod(this, "handleUpdateAxis", Qt::QueuedConnection); 128 | } 129 | 130 | void SpectrogramDisplay::setNumFFTBins(const size_t numBins) 131 | { 132 | _numBins = numBins; 133 | _plotRaster->setNumColumns(numBins); 134 | } 135 | 136 | void SpectrogramDisplay::setWindowType(const std::string &windowType, const std::vector &windowArgs) 137 | { 138 | _fftPowerSpectrum.setWindowType(windowType, windowArgs); 139 | } 140 | 141 | void SpectrogramDisplay::setFullScale(const double fullScale) 142 | { 143 | _fullScale = fullScale; 144 | } 145 | 146 | void SpectrogramDisplay::setFFTMode(const std::string &fftMode) 147 | { 148 | if (fftMode == "REAL"){} 149 | else if (fftMode == "COMPLEX"){} 150 | else if (fftMode == "AUTO"){} 151 | else throw Pothos::InvalidArgumentException("PeriodogramDisplay::setFFTMode("+fftMode+")", "unknown mode"); 152 | _fftModeComplex = (fftMode != "REAL"); 153 | _fftModeAutomatic = (fftMode == "AUTO"); 154 | QMetaObject::invokeMethod(this, "handleUpdateAxis", Qt::QueuedConnection); 155 | } 156 | 157 | void SpectrogramDisplay::setTimeSpan(const double timeSpan) 158 | { 159 | _timeSpan = timeSpan; 160 | QMetaObject::invokeMethod(this, "handleUpdateAxis", Qt::QueuedConnection); 161 | } 162 | 163 | QString SpectrogramDisplay::title(void) const 164 | { 165 | return _mainPlot->title().text(); 166 | } 167 | 168 | void SpectrogramDisplay::setReferenceLevel(const double refLevel) 169 | { 170 | _refLevel = refLevel; 171 | QMetaObject::invokeMethod(this, "handleUpdateAxis", Qt::QueuedConnection); 172 | } 173 | 174 | void SpectrogramDisplay::setDynamicRange(const double dynRange) 175 | { 176 | _dynRange = dynRange; 177 | QMetaObject::invokeMethod(this, "handleUpdateAxis", Qt::QueuedConnection); 178 | } 179 | 180 | void SpectrogramDisplay::handleUpdateAxis(void) 181 | { 182 | QString timeAxisTitle("secs"); 183 | if (_timeSpan <= 100e-9) 184 | { 185 | _timeSpan *= 1e9; 186 | timeAxisTitle = "nsecs"; 187 | } 188 | else if (_timeSpan <= 100e-6) 189 | { 190 | _timeSpan *= 1e6; 191 | timeAxisTitle = "usecs"; 192 | } 193 | else if (_timeSpan <= 100e-3) 194 | { 195 | _timeSpan *= 1e3; 196 | timeAxisTitle = "msecs"; 197 | } 198 | _mainPlot->setAxisTitle(QwtPlot::yLeft, timeAxisTitle); 199 | 200 | QString freqAxisTitle("Hz"); 201 | double factor = std::max(_sampleRate, _centerFreq); 202 | if (factor >= 2e9) 203 | { 204 | factor = 1e9; 205 | freqAxisTitle = "GHz"; 206 | } 207 | else if (factor >= 2e6) 208 | { 209 | factor = 1e6; 210 | freqAxisTitle = "MHz"; 211 | } 212 | else if (factor >= 2e3) 213 | { 214 | factor = 1e3; 215 | freqAxisTitle = "kHz"; 216 | } 217 | _mainPlot->setAxisTitle(QwtPlot::xBottom, freqAxisTitle); 218 | 219 | _mainPlot->zoomer()->setAxis(QwtPlot::xBottom, QwtPlot::yLeft); 220 | 221 | _sampleRateWoAxisUnits = _sampleRate/factor; 222 | _centerFreqWoAxisUnits = _centerFreq/factor; 223 | 224 | //update main plot axis 225 | const qreal freqLow = _fftModeComplex?(_centerFreqWoAxisUnits-_sampleRateWoAxisUnits/2):0.0; 226 | _mainPlot->setAxisScale(QwtPlot::xBottom, freqLow, _centerFreqWoAxisUnits+_sampleRateWoAxisUnits/2); 227 | _mainPlot->setAxisScale(QwtPlot::yLeft, 0, _timeSpan); 228 | _mainPlot->setAxisScale(QwtPlot::yRight, _refLevel-_dynRange, _refLevel); 229 | 230 | _mainPlot->updateAxes(); //update after axis changes before setting raster 231 | _plotRaster->setInterval(Qt::XAxis, _mainPlot->axisInterval(QwtPlot::xBottom)); 232 | _plotRaster->setInterval(Qt::YAxis, _mainPlot->axisInterval(QwtPlot::yLeft)); 233 | _plotRaster->setInterval(Qt::ZAxis, _mainPlot->axisInterval(QwtPlot::yRight)); 234 | _plotRaster->setFFTMode(_fftModeComplex); 235 | _plotSpect->setColorMap(makeQwtColorMap(_colorMapName)); 236 | _mainPlot->axisWidget(QwtPlot::yRight)->setColorMap(_plotRaster->interval(Qt::ZAxis), makeQwtColorMap(_colorMapName)); 237 | 238 | _mainPlot->zoomer()->setZoomBase(); //record current axis settings 239 | } 240 | 241 | void SpectrogramDisplay::enableXAxis(const bool enb) 242 | { 243 | QMetaObject::invokeMethod(_mainPlot, "enableAxis", Qt::QueuedConnection, Q_ARG(int, QwtPlot::xBottom), Q_ARG(bool, enb)); 244 | } 245 | 246 | void SpectrogramDisplay::enableYAxis(const bool enb) 247 | { 248 | QMetaObject::invokeMethod(_mainPlot, "enableAxis", Qt::QueuedConnection, Q_ARG(int, QwtPlot::yLeft), Q_ARG(bool, enb)); 249 | } 250 | 251 | QVariant SpectrogramDisplay::saveState(void) const 252 | { 253 | return _mainPlot->state(); 254 | } 255 | 256 | void SpectrogramDisplay::restoreState(const QVariant &state) 257 | { 258 | _mainPlot->setState(state); 259 | } 260 | 261 | void SpectrogramDisplay::handleZoomed(const QRectF &) 262 | { 263 | return; 264 | } 265 | 266 | void SpectrogramDisplay::handlePickerSelected(const QPointF &p) 267 | { 268 | const double freq = p.x()*_sampleRate/_sampleRateWoAxisUnits; 269 | this->emitSignal("frequencySelected", freq); 270 | this->emitSignal("relativeFrequencySelected", freq - _centerFreq); 271 | } 272 | 273 | void SpectrogramDisplay::appendBins(const std::valarray &bins) 274 | { 275 | _plotRaster->appendBins(bins); 276 | } 277 | 278 | void SpectrogramDisplay::setColorMap(const std::string &colorMapName) 279 | { 280 | _colorMapName = colorMapName; 281 | QMetaObject::invokeMethod(this, "handleUpdateAxis", Qt::QueuedConnection); 282 | } 283 | -------------------------------------------------------------------------------- /Spectrogram/SpectrogramDisplay.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2016 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #pragma once 5 | #include //_USE_MATH_DEFINES 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "PothosPlotterFFTUtils.hpp" 13 | 14 | class QTimer; 15 | class PothosPlotter; 16 | class QwtColorMap; 17 | class QwtPlotSpectrogram; 18 | class MySpectrogramRasterData; 19 | 20 | class SpectrogramDisplay : public QWidget, public Pothos::Block 21 | { 22 | Q_OBJECT 23 | public: 24 | 25 | SpectrogramDisplay(void); 26 | 27 | ~SpectrogramDisplay(void); 28 | 29 | QWidget *widget(void) 30 | { 31 | return this; 32 | } 33 | 34 | //! set the plotter's title 35 | void setTitle(const QString &title); 36 | 37 | /*! 38 | * update rate for the plotter 39 | * how often to update the display 40 | */ 41 | void setDisplayRate(const double displayRate); 42 | 43 | /*! 44 | * sample rate for the plotter 45 | * controls the frequency scaling display 46 | */ 47 | void setSampleRate(const double sampleRate); 48 | 49 | /*! 50 | * center frequency of the plot 51 | */ 52 | void setCenterFrequency(const double freq); 53 | 54 | void setNumFFTBins(const size_t numBins); 55 | void setWindowType(const std::string &, const std::vector &); 56 | void setFullScale(const double fullScale); 57 | void setFFTMode(const std::string &fftMode); 58 | void setTimeSpan(const double timeSpan); 59 | void setReferenceLevel(const double refLevel); 60 | void setDynamicRange(const double dynRange); 61 | 62 | QString title(void) const; 63 | 64 | double displayRate(void) const 65 | { 66 | return _displayRate; 67 | } 68 | 69 | double sampleRate(void) const 70 | { 71 | return _sampleRate; 72 | } 73 | 74 | double centerFrequency(void) const 75 | { 76 | return _centerFreq; 77 | } 78 | 79 | size_t numFFTBins(void) const 80 | { 81 | return _numBins; 82 | } 83 | 84 | double timeSpan(void) const 85 | { 86 | return _timeSpan; 87 | } 88 | 89 | double referenceLevel(void) const 90 | { 91 | return _refLevel; 92 | } 93 | 94 | double dynamicRange(void) const 95 | { 96 | return _dynRange; 97 | } 98 | 99 | void enableXAxis(const bool enb); 100 | void enableYAxis(const bool enb); 101 | 102 | void setColorMap(const std::string &colorMapName); 103 | 104 | void setFreqLabelId(const std::string &id) 105 | { 106 | _freqLabelId = id; 107 | } 108 | 109 | void setRateLabelId(const std::string &id) 110 | { 111 | _rateLabelId = id; 112 | } 113 | 114 | void activate(void); 115 | void deactivate(void); 116 | void work(void); 117 | 118 | //allow for standard resize controls with the default size policy 119 | QSize minimumSizeHint(void) const 120 | { 121 | return QSize(300, 150); 122 | } 123 | QSize sizeHint(void) const 124 | { 125 | return this->minimumSizeHint(); 126 | } 127 | 128 | public slots: 129 | 130 | QVariant saveState(void) const; 131 | 132 | void restoreState(const QVariant &value); 133 | 134 | private slots: 135 | void handleZoomed(const QRectF &); 136 | void handlePickerSelected(const QPointF &); 137 | void appendBins(const std::valarray &bins); 138 | void handleUpdateAxis(void); 139 | 140 | private: 141 | QTimer *_replotTimer; 142 | PothosPlotter *_mainPlot; 143 | std::unique_ptr _plotSpect; 144 | MySpectrogramRasterData *_plotRaster; 145 | FFTPowerSpectrum _fftPowerSpectrum; 146 | double _lastUpdateRate; 147 | double _displayRate; 148 | double _sampleRate; 149 | double _sampleRateWoAxisUnits; 150 | double _centerFreq; 151 | double _centerFreqWoAxisUnits; 152 | size_t _numBins; 153 | double _timeSpan; 154 | double _refLevel; 155 | double _dynRange; 156 | double _fullScale; 157 | bool _fftModeComplex; 158 | bool _fftModeAutomatic; 159 | std::string _freqLabelId; 160 | std::string _rateLabelId; 161 | std::string _colorMapName; 162 | }; 163 | -------------------------------------------------------------------------------- /Spectrogram/SpectrogramRaster.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2016 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #pragma once 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | class MySpectrogramRasterData : public QwtRasterData 12 | { 13 | public: 14 | MySpectrogramRasterData(void): 15 | _numCols(1), 16 | _isComplex(true) 17 | { 18 | this->setNumRows(1); 19 | } 20 | 21 | //! translate a plot coordinate into a raster value 22 | double value(double x, double y) const 23 | { 24 | const auto time = size_t(_yScale*(y-_yOff)); 25 | const auto bin = size_t(_xScale*(x-_xOff)); 26 | return _data[time][bin]; 27 | } 28 | 29 | //! append a new power spectrum bin array 30 | void appendBins(const std::valarray &bins) 31 | { 32 | std::unique_lock lock(_rasterMutex); 33 | _data.push_front(bins); 34 | _data.pop_back(); 35 | } 36 | 37 | //! A raster operation has begun 38 | void initRaster(const QRectF &, const QSize &raster) 39 | { 40 | _rasterMutex.lock(); 41 | 42 | this->setNumRows(raster.height()); 43 | 44 | _yOff = this->interval(Qt::YAxis).minValue(); 45 | _yScale = (_data.size()-1)/this->interval(Qt::YAxis).width(); 46 | if (_isComplex) 47 | { 48 | _xOff = this->interval(Qt::XAxis).minValue(); 49 | _xScale = (_numCols-1)/this->interval(Qt::XAxis).width(); 50 | } 51 | else 52 | { 53 | _xScale = (_numCols/2-1)/this->interval(Qt::XAxis).width(); 54 | _xOff = this->interval(Qt::XAxis).minValue() - this->interval(Qt::XAxis).width(); 55 | } 56 | 57 | } 58 | 59 | //! A raster operation has ended 60 | void discardRaster(void) 61 | { 62 | _rasterMutex.unlock(); 63 | } 64 | 65 | //! Change the number of bins per power spectrum 66 | void setNumColumns(const size_t numCols) 67 | { 68 | std::unique_lock lock(_rasterMutex); 69 | if (numCols == _numCols) return; 70 | _numCols = numCols; 71 | for (auto &row : _data) 72 | { 73 | std::valarray newRow(_numCols); 74 | for (size_t i = 0; i < newRow.size(); i++) 75 | newRow[i] = row[size_t((double(i)*(row.size()-1))/(newRow.size()-1))]; 76 | row = newRow; 77 | } 78 | } 79 | 80 | //! Set the rendering mode for real valued signals 81 | void setFFTMode(const bool isComplex) 82 | { 83 | std::unique_lock lock(_rasterMutex); 84 | _isComplex = isComplex; 85 | } 86 | 87 | private: 88 | void setNumRows(const int num) 89 | { 90 | if (_data.isEmpty()) _data.push_front(std::valarray(-1000, _numCols)); 91 | while (_data.size() > num) _data.pop_back(); 92 | while (_data.size() < num) _data.push_front(_data.front()); 93 | } 94 | 95 | //raster scale+adjustment factors 96 | float _yOff, _yScale, _xOff, _xScale; 97 | 98 | //raw data for the entire raster 99 | QList> _data; 100 | 101 | //thread-safe access mutex 102 | std::mutex _rasterMutex; 103 | 104 | size_t _numCols; 105 | bool _isComplex; 106 | }; 107 | -------------------------------------------------------------------------------- /Spectrogram/SpectrogramWork.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2016 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include "SpectrogramDisplay.hpp" 5 | #include 6 | #include 7 | #include 8 | 9 | /*********************************************************************** 10 | * initialization functions 11 | **********************************************************************/ 12 | void SpectrogramDisplay::activate(void) 13 | { 14 | QMetaObject::invokeMethod(_replotTimer, "start", Qt::QueuedConnection); 15 | } 16 | 17 | void SpectrogramDisplay::deactivate(void) 18 | { 19 | QMetaObject::invokeMethod(_replotTimer, "stop", Qt::QueuedConnection); 20 | } 21 | 22 | /*********************************************************************** 23 | * work function 24 | **********************************************************************/ 25 | void SpectrogramDisplay::work(void) 26 | { 27 | auto updateRate = this->height()/_timeSpan; 28 | if (updateRate != _lastUpdateRate) this->call("updateRateChanged", updateRate); 29 | _lastUpdateRate = updateRate; 30 | 31 | auto inPort = this->input(0); 32 | if (not inPort->hasMessage()) return; 33 | const auto msg = inPort->popMessage(); 34 | 35 | //label-based messages have in-line commands 36 | if (msg.type() == typeid(Pothos::Label)) 37 | { 38 | const auto &label = msg.convert(); 39 | if (label.id == _freqLabelId and label.data.canConvert(typeid(double))) 40 | { 41 | this->setCenterFrequency(label.data.convert()); 42 | } 43 | if (label.id == _rateLabelId and label.data.canConvert(typeid(double))) 44 | { 45 | this->setSampleRate(label.data.convert()); 46 | } 47 | } 48 | 49 | //packet-based messages have payloads to FFT 50 | if (msg.type() == typeid(Pothos::Packet)) 51 | { 52 | const auto &buff = msg.convert().payload; 53 | auto floatBuff = buff.convert(Pothos::DType(typeid(std::complex)), buff.elements()); 54 | 55 | //safe guard against FFT size changes, old buffers could still be in-flight 56 | if (floatBuff.elements() != this->numFFTBins()) return; 57 | 58 | //handle automatic FFT mode 59 | if (_fftModeAutomatic) 60 | { 61 | const bool isComplex = buff.dtype.isComplex(); 62 | const bool changed = _fftModeComplex != isComplex; 63 | _fftModeComplex = isComplex; 64 | if (changed) QMetaObject::invokeMethod(this, "handleUpdateAxis", Qt::QueuedConnection); 65 | } 66 | 67 | //power bins to points on the curve 68 | CArray fftBins(floatBuff.as *>(), this->numFFTBins()); 69 | const auto powerBins = _fftPowerSpectrum.transform(fftBins, _fullScale); 70 | this->appendBins(powerBins); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Spectrogram/gen_colormaps.py: -------------------------------------------------------------------------------- 1 | """ 2 | Reference for colormaps included with Matplotlib. 3 | 4 | This reference example shows all colormaps included with Matplotlib. Note that 5 | any colormap listed here can be reversed by appending "_r" (e.g., "pink_r"). 6 | These colormaps are divided into the following categories: 7 | 8 | Sequential: 9 | These colormaps are approximately monochromatic colormaps varying smoothly 10 | between two color tones---usually from low saturation (e.g. white) to high 11 | saturation (e.g. a bright blue). Sequential colormaps are ideal for 12 | representing most scientific data since they show a clear progression from 13 | low-to-high values. 14 | 15 | Diverging: 16 | These colormaps have a median value (usually light in color) and vary 17 | smoothly to two different color tones at high and low values. Diverging 18 | colormaps are ideal when your data has a median value that is significant 19 | (e.g. 0, such that positive and negative values are represented by 20 | different colors of the colormap). 21 | 22 | Qualitative: 23 | These colormaps vary rapidly in color. Qualitative colormaps are useful for 24 | choosing a set of discrete colors. For example:: 25 | 26 | color_list = plt.cm.Set3(np.linspace(0, 1, 12)) 27 | 28 | gives a list of RGB colors that are good for plotting a series of lines on 29 | a dark background. 30 | 31 | Miscellaneous: 32 | Colormaps that don't fit into the categories above. 33 | 34 | """ 35 | import numpy as np 36 | import matplotlib.pyplot as plt 37 | 38 | # Have colormaps separated into categories: 39 | # http://matplotlib.org/examples/color/colormaps_reference.html 40 | 41 | cmaps = [#('Perceptually Uniform Sequential', 42 | # ['viridis', 'inferno', 'plasma', 'magma']), 43 | ('Sequential', ['Blues', 'BuGn', 'BuPu', 44 | 'GnBu', 'Greens', 'Greys', 'Oranges', 'OrRd', 45 | 'PuBu', 'PuBuGn', 'PuRd', 'Purples', 'RdPu', 46 | 'Reds', 'YlGn', 'YlGnBu', 'YlOrBr', 'YlOrRd']), 47 | ('Sequential (2)', ['afmhot', 'autumn', 'bone', 'cool', 48 | 'copper', 'gist_heat', 'gray', 'hot', 49 | 'pink', 'spring', 'summer', 'winter']), 50 | ('Diverging', ['BrBG', 'bwr', 'coolwarm', 'PiYG', 'PRGn', 'PuOr', 51 | 'RdBu', 'RdGy', 'RdYlBu', 'RdYlGn', 'Spectral', 52 | 'seismic']), 53 | ('Qualitative', ['Accent', 'Dark2', 'Paired', 'Pastel1', 54 | 'Pastel2', 'Set1', 'Set2', 'Set3']), 55 | ('Miscellaneous', ['gist_earth', 'terrain', 'ocean', 'gist_stern', 56 | 'brg', 'CMRmap', 'cubehelix', 57 | 'gnuplot', 'gnuplot2', 'gist_ncar', 58 | 'nipy_spectral', 'jet', 'rainbow', 59 | 'gist_rainbow', 'hsv', 'flag', 'prism'])] 60 | 61 | 62 | nrows = max(len(cmap_list) for cmap_category, cmap_list in cmaps) 63 | gradient = np.linspace(0, 1, 256) 64 | gradient = np.vstack((gradient, gradient)) 65 | 66 | def interp_subspec(pt, subspec): 67 | x0 = None 68 | y0 = None 69 | x1 = None 70 | y1 = None 71 | 72 | for x, y0_, y1_ in subspec: 73 | if x <= pt: 74 | x0 = x 75 | y0 = y0_ 76 | if x > pt: 77 | x1 = x 78 | y1 = y1_ 79 | break 80 | 81 | if x1 is None: 82 | x1 = subspec[-1][0] 83 | y1 = subspec[-1][2] 84 | 85 | if pt == x0: return y0 86 | if pt == x1: return y1 87 | 88 | assert(x0 is not None) 89 | assert(x1 is not None) 90 | assert(y0 is not None) 91 | assert(y1 is not None) 92 | #print map(type, [pt, y0, x0, x1, y1]) 93 | return y0 + (y1-y0)*(pt-x0)/(x1-x0) 94 | 95 | def check(name, result): 96 | for line in result: 97 | for i, elem in enumerate(line): 98 | if elem > 1.0: 99 | line[i] = 1.0 100 | if elem < 0.0: 101 | line[i] = 0.0 102 | 103 | def spec_to_lin(name, spec): 104 | 105 | if hasattr(spec['red'], '__call__'): 106 | linMap = list() 107 | x = np.arange(0.0, 1.0+0.01, 0.01) 108 | reds = spec['red'](x) 109 | greens = spec['green'](x) 110 | blues = spec['blue'](x) 111 | if 'alpha' in spec: 112 | alphas = spec['alpha'](x) 113 | for i, x in enumerate(x): 114 | data = [x, reds[i], greens[i], blues[i]] 115 | if 'alpha' in spec: 116 | data.append(alphas[i]) 117 | linMap.append(data) 118 | check(name, linMap) 119 | return linMap 120 | 121 | allPts = list() 122 | for key, subspec in spec.iteritems(): 123 | for x, y0, y1 in subspec: 124 | allPts.append(x) 125 | 126 | linMap = list() 127 | for pt in sorted(set(allPts)): 128 | data = [pt, 129 | interp_subspec(pt, spec['red']), 130 | interp_subspec(pt, spec['green']), 131 | interp_subspec(pt, spec['blue'])] 132 | if 'alpha' in spec: 133 | data.append(interp_subspec(pt, spec['alpha'])) 134 | linMap.append(data) 135 | 136 | check(name, linMap) 137 | return linMap 138 | 139 | def plot_color_gradients(cmap_category, cmap_list): 140 | #fig, axes = plt.subplots(nrows=nrows) 141 | #fig.subplots_adjust(top=0.95, bottom=0.01, left=0.2, right=0.99) 142 | #axes[0].set_title(cmap_category + ' colormaps', fontsize=14) 143 | 144 | #for ax, name in zip(axes, cmap_list): 145 | for name in cmap_list: 146 | try: 147 | #ax.imshow(gradient, aspect='auto', cmap=plt.get_cmap(name)) 148 | #pos = list(ax.get_position().bounds) 149 | #x_text = pos[0] - 0.01 150 | #y_text = pos[1] + pos[3]/2. 151 | #fig.text(x_text, y_text, name, va='center', ha='right', fontsize=10) 152 | yield name, spec_to_lin(name, plt.get_cmap(name)._segmentdata) 153 | except Exception as ex: 154 | print '#'*40,name,str(ex),plt.get_cmap(name)._segmentdata 155 | 156 | # Turn off *all* ticks & spines, not just the ones with colormaps. 157 | #for ax in axes: 158 | # ax.set_axis_off() 159 | 160 | f = open("GeneratedColorMaps.cpp", 'w') 161 | titles = list() 162 | names = list() 163 | 164 | f.write('//\n') 165 | f.write('//Machine generated color maps extracted from matplotlib cm module\n') 166 | f.write('//\n') 167 | f.write('\n') 168 | f.write('#include "GeneratedColorMaps.hpp"\n') 169 | f.write('\n') 170 | 171 | for cmap_category, cmap_list in cmaps: 172 | for name, data in plot_color_gradients(cmap_category, cmap_list): 173 | title = (cmap_category + '/' + name).replace("_", ' ').title() 174 | f.write('static std::vector> makeColorMap%s(void)\n'%name) 175 | f.write('{\n') 176 | f.write(' std::vector> out;\n') 177 | for d in data: 178 | args = ','.join(map(str, d)) 179 | f.write(' out.push_back(std::vector{%s});\n'%args) 180 | f.write(' return out;\n') 181 | f.write('}\n') 182 | f.write('\n') 183 | titles.append(title) 184 | names.append(name) 185 | 186 | f.write('\n') 187 | f.write('std::vector> availableColorMaps(void)') 188 | f.write('{\n') 189 | f.write(' std::vector> out;\n') 190 | for title, name in zip(titles, names): 191 | f.write(' out.emplace_back("%s", "%s");\n'%(title, name)); 192 | f.write(' return out;\n') 193 | f.write('}\n') 194 | f.write('\n') 195 | f.write('std::vector> lookupColorMap(const std::string &name)\n') 196 | f.write('{\n') 197 | for name in names: 198 | f.write(' if (name == "%s") return makeColorMap%s();\n'%(name, name)) 199 | 200 | f.write(' return std::vector>();\n') 201 | f.write('}\n') 202 | 203 | 204 | #plt.show() 205 | -------------------------------------------------------------------------------- /WaveMonitor/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ######################################################################## 2 | ## Feature registration 3 | ######################################################################## 4 | cmake_dependent_option(ENABLE_PLOTTERS_WAVEMONITOR "Enable Pothos Plotters.WaveMonitor component" ON "ENABLE_PLOTTERS" OFF) 5 | add_feature_info(" Wave Monitor" ENABLE_PLOTTERS_WAVEMONITOR "Wavemonitor time-domain waveform plotter") 6 | if (NOT ENABLE_PLOTTERS_WAVEMONITOR) 7 | return() 8 | endif() 9 | 10 | ######################################################################## 11 | # Build time domain plot module 12 | ######################################################################## 13 | POTHOS_MODULE_UTIL( 14 | TARGET WaveMonitor 15 | SOURCES 16 | WaveMonitor.cpp 17 | WaveMonitorWork.cpp 18 | WaveMonitorDisplay.cpp 19 | DOC_SOURCES WaveMonitor.cpp 20 | LIBRARIES 21 | Qt${QT_VERSION_MAJOR}::Widgets 22 | PothosQwt 23 | PothosPlotterUtils 24 | DESTINATION plotters 25 | ) 26 | -------------------------------------------------------------------------------- /WaveMonitor/WaveMonitor.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2017 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include "WaveMonitorDisplay.hpp" 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | /*********************************************************************** 11 | * |PothosDoc Wave Monitor 12 | * 13 | * The wave monitor plot displays a live two dimensional plot of input elements vs time. 14 | * 15 | * |category /Plotters 16 | * |keywords time plot wave scope 17 | * |alias /widgets/wave_monitor 18 | * 19 | * |param title The title of the plot 20 | * |default "Amplitude vs Time" 21 | * |widget StringEntry() 22 | * |preview valid 23 | * 24 | * |param numInputs[Num Inputs] The number of input ports. 25 | * |default 1 26 | * |widget SpinBox(minimum=1) 27 | * |preview disable 28 | * 29 | * |param displayRate[Display Rate] How often the plotter updates. 30 | * |default 10.0 31 | * |units updates/sec 32 | * |preview disable 33 | * 34 | * |param sampleRate[Sample Rate] The rate of the input elements. 35 | * |default 1e6 36 | * |units samples/sec 37 | * 38 | * |param numPoints[Num Points] The number of points per plot capture. 39 | * |default 1024 40 | * |preview disable 41 | * 42 | * |param align[Alignment] Ensure that multiple channels are aligned. 43 | * All channels must have matching sample rates when alignment is enabled. 44 | * |default false 45 | * |option [Disable] false 46 | * |option [Enable] true 47 | * |preview disable 48 | * 49 | * |param autoScale[Auto-Scale] Enable automatic scaling for the vertical axis. 50 | * |default true 51 | * |option [Auto scale] true 52 | * |option [Use limits] false 53 | * |preview disable 54 | * |tab Axis 55 | * 56 | * |param yRange[Y-Axis Range] The minimum and maximum values for the Y-Axis. 57 | * When auto scale is off, this parameter controls the vertical axis. 58 | * |default [-1.0, 1.0] 59 | * |preview disable 60 | * |tab Axis 61 | * 62 | * |param enableXAxis[Enable X-Axis] Show or hide the horizontal axis markers. 63 | * |option [Show] true 64 | * |option [Hide] false 65 | * |default true 66 | * |preview disable 67 | * |tab Axis 68 | * 69 | * |param enableYAxis[Enable Y-Axis] Show or hide the vertical axis markers. 70 | * |option [Show] true 71 | * |option [Hide] false 72 | * |default true 73 | * |preview disable 74 | * |tab Axis 75 | * 76 | * |param yAxisTitle[Y-Axis Title] The title of the verical axis. 77 | * |default "" 78 | * |widget StringEntry() 79 | * |preview disable 80 | * |tab Axis 81 | * 82 | * |param triggerSource[Source] Which input channel to monitor for trigger events. 83 | * |default 0 84 | * |widget SpinBox(minimum=0) 85 | * |preview disable 86 | * |tab Trigger 87 | * 88 | * |param triggerWindows[Windows] The number of trigger windows per output event. 89 | * A single output event can be composed of multiple back-to-back trigger windows. 90 | * |default 1 91 | * |widget SpinBox(minimum=1) 92 | * |preview disable 93 | * |tab Trigger 94 | * 95 | * |param triggerHoldOff[Hold Off] Hold-off subsequent trigger events for this many samples. 96 | * After a trigger event occurs, hold off disables trigger search until 97 | * the specified number of samples has been consumed on all input ports. 98 | * Hold-off is most useful when multiple trigger windows are used. 99 | * |units samples 100 | * |default 0 101 | * |preview disable 102 | * |tab Trigger 103 | * 104 | * |param triggerSlope[Slope] The required slope of the trigger detection. 105 | *
    106 | *
  • Positive slope means that the trigger will be activated when the level is rises above the specified trigger level.
  • 107 | *
  • Negative slope means that the trigger will be activated when the level is falls below the specified trigger level.
  • 108 | *
  • Level means that the trigger will be activated when the trigger level is detected, regardless of the slope.
  • 109 | *
110 | * |default "POS" 111 | * |option [Positive] "POS" 112 | * |option [Negative] "NEG" 113 | * |option [Level] "LEVEL" 114 | * |preview disable 115 | * |tab Trigger 116 | * 117 | * |param triggerMode [Mode] The operational mode of the triggering system. 118 | *
    119 | *
  • In automatic mode, the trigger event is forced by timer if none occurs.
  • 120 | *
  • In semi-automatic mode, the trigger event is forced by timer after the first window.
  • 121 | *
  • In normal mode, samples are only forwarded when a trigger event occurs.
  • 122 | *
  • In periodic mode, there is no trigger search, the trigger event is forced by timer.
  • 123 | *
  • In disabled mode, trigger search is disabled and samples are not forwarded.
  • 124 | *
125 | * |default "AUTOMATIC" 126 | * |option [Automatic] "AUTOMATIC" 127 | * |option [Semi-automatic] "SEMIAUTOMATIC" 128 | * |option [Normal] "NORMAL" 129 | * |option [Periodic] "PERIODIC" 130 | * |option [Disabled] "DISABLED" 131 | * |preview disable 132 | * |tab Trigger 133 | * 134 | * |param triggerLevel [Level] The value of the input required for a trigger event. 135 | * |default 0.0 136 | * |widget DoubleSpinBox() 137 | * |preview disable 138 | * |tab Trigger 139 | * 140 | * |param triggerPosition [Position] The offset in samples before the trigger event. 141 | * When the samples are forwarded to the output, 142 | * the trigger event occurs position number of samples into the array. 143 | * |units samples 144 | * |default 0 145 | * |widget SpinBox(minimum=0) 146 | * |preview disable 147 | * |tab Trigger 148 | * 149 | * |param triggerLabelId [Label ID] An optional label ID that causes a trigger event. 150 | * Rather than an input level, an associated stream label can indicate a trigger event. 151 | * The trigger label simply overrides the level-trigger, all other rules still apply. 152 | * An empty label ID disables this feature. 153 | * |default "" 154 | * |widget StringEntry() 155 | * |preview disable 156 | * |tab Trigger 157 | * 158 | * |param label0[Ch0 Label] The display label for channel 0. 159 | * |default "" 160 | * |widget StringEntry() 161 | * |preview disable 162 | * |tab Channels 163 | * 164 | * |param style0[Ch0 Style] The curve style for channel 0. 165 | * |default "LINE" 166 | * |option [Line] "LINE" 167 | * |option [Dash] "DASH" 168 | * |option [Dots] "DOTS" 169 | * |preview disable 170 | * |tab Channels 171 | * 172 | * |param label1[Ch1 Label] The display label for channel 1. 173 | * |default "" 174 | * |widget StringEntry() 175 | * |preview disable 176 | * |tab Channels 177 | * 178 | * |param style1[Ch1 Style] The curve style for channel 1. 179 | * |default "LINE" 180 | * |option [Line] "LINE" 181 | * |option [Dash] "DASH" 182 | * |option [Dots] "DOTS" 183 | * |preview disable 184 | * |tab Channels 185 | * 186 | * |param label2[Ch2 Label] The display label for channel 2. 187 | * |default "" 188 | * |widget StringEntry() 189 | * |preview disable 190 | * |tab Channels 191 | * 192 | * |param style2[Ch2 Style] The curve style for channel 2. 193 | * |default "LINE" 194 | * |option [Line] "LINE" 195 | * |option [Dash] "DASH" 196 | * |option [Dots] "DOTS" 197 | * |preview disable 198 | * |tab Channels 199 | * 200 | * |param label3[Ch3 Label] The display label for channel 3. 201 | * |default "" 202 | * |widget StringEntry() 203 | * |preview disable 204 | * |tab Channels 205 | * 206 | * |param style3[Ch3 Style] The curve style for channel 3. 207 | * |default "LINE" 208 | * |option [Line] "LINE" 209 | * |option [Dash] "DASH" 210 | * |option [Dots] "DOTS" 211 | * |preview disable 212 | * |tab Channels 213 | * 214 | * |param rateLabelId[Rate Label ID] Labels with this ID can be used to set the sample rate. 215 | * To ignore sample rate labels, set this parameter to an empty string. 216 | * |default "rxRate" 217 | * |widget StringEntry() 218 | * |preview disable 219 | * |tab Labels 220 | * 221 | * |mode graphWidget 222 | * |factory /plotters/wave_monitor(remoteEnv) 223 | * |initializer setNumInputs(numInputs) 224 | * |setter setTitle(title) 225 | * |setter setDisplayRate(displayRate) 226 | * |setter setSampleRate(sampleRate) 227 | * |setter setNumPoints(numPoints) 228 | * |setter setAlignment(align) 229 | * |setter setAutoScale(autoScale) 230 | * |setter setYRange(yRange) 231 | * |setter enableXAxis(enableXAxis) 232 | * |setter enableYAxis(enableYAxis) 233 | * |setter setYAxisTitle(yAxisTitle) 234 | * |setter setTriggerSource(triggerSource) 235 | * |setter setTriggerWindows(triggerWindows) 236 | * |setter setTriggerHoldOff(triggerHoldOff) 237 | * |setter setTriggerSlope(triggerSlope) 238 | * |setter setTriggerMode(triggerMode) 239 | * |setter setTriggerLevel(triggerLevel) 240 | * |setter setTriggerPosition(triggerPosition) 241 | * |setter setTriggerLabelId(triggerLabelId) 242 | * |setter setChannelLabel(0, label0) 243 | * |setter setChannelStyle(0, style0) 244 | * |setter setChannelLabel(1, label1) 245 | * |setter setChannelStyle(1, style1) 246 | * |setter setChannelLabel(2, label2) 247 | * |setter setChannelStyle(2, style2) 248 | * |setter setChannelLabel(3, label3) 249 | * |setter setChannelStyle(3, style3) 250 | * |setter setRateLabelId(rateLabelId) 251 | **********************************************************************/ 252 | class WaveMonitor : public Pothos::Topology 253 | { 254 | public: 255 | static Topology *make(const Pothos::ProxyEnvironment::Sptr &remoteEnv) 256 | { 257 | return new WaveMonitor(remoteEnv); 258 | } 259 | 260 | WaveMonitor(const Pothos::ProxyEnvironment::Sptr &remoteEnv) 261 | { 262 | _display.reset(new WaveMonitorDisplay()); 263 | _display->setName("Display"); 264 | 265 | auto registry = remoteEnv->findProxy("Pothos/BlockRegistry"); 266 | _trigger = registry.call("/comms/wave_trigger"); 267 | _trigger.call("setName", "Trigger"); 268 | 269 | //register calls in this topology 270 | this->registerCall(this, POTHOS_FCN_TUPLE(WaveMonitor, setNumInputs)); 271 | this->registerCall(this, POTHOS_FCN_TUPLE(WaveMonitor, setRateLabelId)); 272 | 273 | //display setters 274 | _topologyToDisplaySetter["setTitle"] = "setTitle"; 275 | _topologyToDisplaySetter["setSampleRate"] = "setSampleRate"; 276 | _topologyToDisplaySetter["setNumPoints"] = "setNumPoints"; 277 | _topologyToDisplaySetter["setAutoScale"] = "setAutoScale"; 278 | _topologyToDisplaySetter["setYRange"] = "setYRange"; 279 | _topologyToDisplaySetter["enableXAxis"] = "enableXAxis"; 280 | _topologyToDisplaySetter["enableYAxis"] = "enableYAxis"; 281 | _topologyToDisplaySetter["setYAxisTitle"] = "setYAxisTitle"; 282 | _topologyToDisplaySetter["setChannelLabel"] = "setChannelLabel"; 283 | _topologyToDisplaySetter["setChannelStyle"] = "setChannelStyle"; 284 | _topologyToDisplaySetter["clearChannels"] = "clearChannels"; 285 | 286 | //trigger setters 287 | _topologyToTriggerSetter["setDisplayRate"] = "setEventRate"; 288 | _topologyToTriggerSetter["setNumPoints"] = "setNumPoints"; 289 | _topologyToTriggerSetter["setAlignment"] = "setAlignment"; 290 | _topologyToTriggerSetter["setTriggerSource"] = "setSource"; 291 | _topologyToTriggerSetter["setTriggerWindows"] = "setNumWindows"; 292 | _topologyToTriggerSetter["setTriggerHoldOff"] = "setHoldOff"; 293 | _topologyToTriggerSetter["setTriggerSlope"] = "setSlope"; 294 | _topologyToTriggerSetter["setTriggerMode"] = "setMode"; 295 | _topologyToTriggerSetter["setTriggerLevel"] = "setLevel"; 296 | _topologyToTriggerSetter["setTriggerPosition"] = "setPosition"; 297 | _topologyToTriggerSetter["setTriggerLabelId"] = "setLabelId"; 298 | 299 | //connect to internal display block 300 | for (const auto &pair : _topologyToDisplaySetter) 301 | { 302 | this->connect(this, pair.first, _display, pair.second); 303 | } 304 | 305 | //connect to the internal trigger block 306 | for (const auto &pair : _topologyToTriggerSetter) 307 | { 308 | this->connect(this, pair.first, _trigger, pair.second); 309 | } 310 | 311 | //connect stream ports 312 | this->connect(_trigger, 0, _display, 0); 313 | } 314 | 315 | Pothos::Object opaqueCallMethod(const std::string &name, const Pothos::Object *inputArgs, const size_t numArgs) const 316 | { 317 | //calls that go to the topology 318 | try 319 | { 320 | return Pothos::Topology::opaqueCallMethod(name, inputArgs, numArgs); 321 | } 322 | catch (const Pothos::BlockCallNotFound &){} 323 | 324 | bool setterCalled = false; 325 | 326 | //is this a display setter? 327 | const auto _displaySetter = _topologyToDisplaySetter.find(name); 328 | if (_displaySetter != _topologyToDisplaySetter.end()) 329 | { 330 | _display->opaqueCallMethod(_displaySetter->second, inputArgs, numArgs); 331 | setterCalled = true; 332 | } 333 | 334 | //is this a trigger setter? 335 | const auto _triggerSetter = _topologyToTriggerSetter.find(name); 336 | if (_triggerSetter != _topologyToTriggerSetter.end() and numArgs == 1) 337 | { 338 | _trigger.call(_triggerSetter->second, _trigger.getEnvironment()->convertObjectToProxy(inputArgs[0])); 339 | setterCalled = true; 340 | } 341 | 342 | if (setterCalled) return Pothos::Object(); 343 | 344 | //forward everything else to display 345 | return _display->opaqueCallMethod(name, inputArgs, numArgs); 346 | } 347 | 348 | void setNumInputs(const size_t numInputs) 349 | { 350 | _trigger.call("setNumPorts", numInputs); 351 | for (size_t i = 0; i < numInputs; i++) 352 | { 353 | this->connect(this, i, _trigger, i); 354 | } 355 | } 356 | 357 | void setRateLabelId(const std::string &id) 358 | { 359 | _display->setRateLabelId(id); 360 | std::vector ids; 361 | if (not id.empty()) ids.push_back(id); 362 | _trigger.call("setIdsList", ids); 363 | } 364 | 365 | private: 366 | Pothos::Proxy _trigger; 367 | std::shared_ptr _display; 368 | 369 | std::map _topologyToTriggerSetter; 370 | std::map _topologyToDisplaySetter; 371 | }; 372 | 373 | /*********************************************************************** 374 | * registration 375 | **********************************************************************/ 376 | static Pothos::BlockRegistry registerWaveMonitor( 377 | "/plotters/wave_monitor", &WaveMonitor::make); 378 | 379 | static Pothos::BlockRegistry registerWaveMonitorOldPath( 380 | "/widgets/wave_monitor", &WaveMonitor::make); 381 | -------------------------------------------------------------------------------- /WaveMonitor/WaveMonitorDisplay.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2021 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include "WaveMonitorDisplay.hpp" 5 | #include "PothosPlotter.hpp" 6 | #include "PothosPlotStyler.hpp" 7 | #include "PothosPlotUtils.hpp" 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | WaveMonitorDisplay::WaveMonitorDisplay(void): 19 | _mainPlot(new PothosPlotter(this, POTHOS_PLOTTER_GRID | POTHOS_PLOTTER_ZOOM)), 20 | _sampleRate(1.0), 21 | _sampleRateWoAxisUnits(1.0), 22 | _numPoints(1024), 23 | _autoScale(false), 24 | _rateLabelId("rxRate"), 25 | _curveCount(0) 26 | { 27 | //setup block 28 | this->registerCall(this, POTHOS_FCN_TUPLE(WaveMonitorDisplay, widget)); 29 | this->registerCall(this, POTHOS_FCN_TUPLE(WaveMonitorDisplay, setTitle)); 30 | this->registerCall(this, POTHOS_FCN_TUPLE(WaveMonitorDisplay, setSampleRate)); 31 | this->registerCall(this, POTHOS_FCN_TUPLE(WaveMonitorDisplay, setNumPoints)); 32 | this->registerCall(this, POTHOS_FCN_TUPLE(WaveMonitorDisplay, numInputs)); 33 | this->registerCall(this, POTHOS_FCN_TUPLE(WaveMonitorDisplay, title)); 34 | this->registerCall(this, POTHOS_FCN_TUPLE(WaveMonitorDisplay, sampleRate)); 35 | this->registerCall(this, POTHOS_FCN_TUPLE(WaveMonitorDisplay, numPoints)); 36 | this->registerCall(this, POTHOS_FCN_TUPLE(WaveMonitorDisplay, setAutoScale)); 37 | this->registerCall(this, POTHOS_FCN_TUPLE(WaveMonitorDisplay, setYRange)); 38 | this->registerCall(this, POTHOS_FCN_TUPLE(WaveMonitorDisplay, enableXAxis)); 39 | this->registerCall(this, POTHOS_FCN_TUPLE(WaveMonitorDisplay, enableYAxis)); 40 | this->registerCall(this, POTHOS_FCN_TUPLE(WaveMonitorDisplay, setYAxisTitle)); 41 | this->registerCall(this, POTHOS_FCN_TUPLE(WaveMonitorDisplay, setChannelLabel)); 42 | this->registerCall(this, POTHOS_FCN_TUPLE(WaveMonitorDisplay, setChannelStyle)); 43 | this->registerCall(this, POTHOS_FCN_TUPLE(WaveMonitorDisplay, clearChannels)); 44 | this->registerSlot("clearChannels"); 45 | this->registerCall(this, POTHOS_FCN_TUPLE(WaveMonitorDisplay, setRateLabelId)); 46 | this->setupInput(0); 47 | 48 | //layout 49 | auto layout = new QHBoxLayout(this); 50 | layout->setSpacing(0); 51 | layout->setContentsMargins(QMargins()); 52 | layout->addWidget(_mainPlot); 53 | 54 | //setup plotter 55 | { 56 | connect(_mainPlot->zoomer(), &QwtPlotZoomer::zoomed, this, &WaveMonitorDisplay::handleZoomed); 57 | } 58 | 59 | //register types passed to gui thread from work 60 | qRegisterMetaType("Pothos::Packet"); 61 | 62 | //setup trigger marker label 63 | _triggerMarkerLabel = PothosMarkerLabel("T"); 64 | static const QColor orange("#FFA500"); 65 | _triggerMarkerLabel.setBackgroundBrush(QBrush(orange)); 66 | } 67 | 68 | WaveMonitorDisplay::~WaveMonitorDisplay(void) 69 | { 70 | return; 71 | } 72 | 73 | void WaveMonitorDisplay::setTitle(const QString &title) 74 | { 75 | QMetaObject::invokeMethod(_mainPlot, "setTitle", Qt::QueuedConnection, Q_ARG(QString, title)); 76 | } 77 | 78 | void WaveMonitorDisplay::setSampleRate(const double sampleRate) 79 | { 80 | _sampleRate = sampleRate; 81 | QMetaObject::invokeMethod(this, "handleUpdateAxis", Qt::QueuedConnection); 82 | } 83 | 84 | void WaveMonitorDisplay::setNumPoints(const size_t numPoints) 85 | { 86 | _numPoints = numPoints; 87 | QMetaObject::invokeMethod(this, "handleUpdateAxis", Qt::QueuedConnection); 88 | } 89 | 90 | QString WaveMonitorDisplay::title(void) const 91 | { 92 | return _mainPlot->title().text(); 93 | } 94 | 95 | void WaveMonitorDisplay::setAutoScale(const bool autoScale) 96 | { 97 | _autoScale = autoScale; 98 | QMetaObject::invokeMethod(this, "handleUpdateAxis", Qt::QueuedConnection); 99 | } 100 | 101 | void WaveMonitorDisplay::setYRange(const std::vector &range) 102 | { 103 | if (range.size() != 2) throw Pothos::RangeException("WaveMonitorDisplay::setYRange()", "range vector must be size 2"); 104 | _yRange = range; 105 | QMetaObject::invokeMethod(this, "handleUpdateAxis", Qt::QueuedConnection); 106 | } 107 | 108 | void WaveMonitorDisplay::enableXAxis(const bool enb) 109 | { 110 | QMetaObject::invokeMethod(_mainPlot, "enableAxis", Qt::QueuedConnection, Q_ARG(int, QwtPlot::xBottom), Q_ARG(bool, enb)); 111 | } 112 | 113 | void WaveMonitorDisplay::enableYAxis(const bool enb) 114 | { 115 | QMetaObject::invokeMethod(_mainPlot, "enableAxis", Qt::QueuedConnection, Q_ARG(int, QwtPlot::yLeft), Q_ARG(bool, enb)); 116 | } 117 | 118 | void WaveMonitorDisplay::setYAxisTitle(const QString &title) 119 | { 120 | QMetaObject::invokeMethod(_mainPlot, "setAxisTitle", Qt::QueuedConnection, Q_ARG(int, QwtPlot::yLeft), Q_ARG(QString, title)); 121 | } 122 | 123 | void WaveMonitorDisplay::handleUpdateAxis(void) 124 | { 125 | if (_yRange.size() == 2) _mainPlot->setAxisScale(QwtPlot::yLeft, _yRange[0], _yRange[1]); 126 | 127 | QString timeAxisTitle("secs"); 128 | double factor = 1.0; 129 | 130 | double timeSpan = _numPoints/_sampleRate; 131 | if (timeSpan <= 100e-9) 132 | { 133 | factor = 1e9; 134 | timeAxisTitle = "nsecs"; 135 | } 136 | else if (timeSpan <= 100e-6) 137 | { 138 | factor = 1e6; 139 | timeAxisTitle = "usecs"; 140 | } 141 | else if (timeSpan <= 100e-3) 142 | { 143 | factor = 1e3; 144 | timeAxisTitle = "msecs"; 145 | } 146 | _mainPlot->setAxisTitle(QwtPlot::xBottom, timeAxisTitle); 147 | 148 | _mainPlot->zoomer()->setAxis(QwtPlot::xBottom, QwtPlot::yLeft); 149 | _sampleRateWoAxisUnits = _sampleRate/factor; 150 | _mainPlot->setAxisScale(QwtPlot::xBottom, 0, _numPoints/_sampleRateWoAxisUnits); 151 | _mainPlot->updateAxes(); //update after axis changes 152 | _mainPlot->zoomer()->setZoomBase(); //record current axis settings 153 | this->handleZoomed(_mainPlot->zoomer()->zoomBase()); //reload 154 | } 155 | 156 | void WaveMonitorDisplay::handleUpdateCurves(void) 157 | { 158 | size_t count = 0; 159 | 160 | for (auto &pair : _curves) 161 | { 162 | const auto &index = pair.first; 163 | auto &curves = pair.second; 164 | const auto &label = _channelLabels[index]; 165 | const auto &styleStr = _channelStyles[index]; 166 | 167 | QwtPlotCurve::CurveStyle style(QwtPlotCurve::Dots); 168 | Qt::PenStyle penStyle(Qt::SolidLine); 169 | qreal width = 1.0; 170 | if (styleStr == "LINE") {style = QwtPlotCurve::Lines; penStyle = Qt::SolidLine; width = 1.0;} 171 | if (styleStr == "DASH") {style = QwtPlotCurve::Lines; penStyle = Qt::DashLine; width = 1.0;} 172 | if (styleStr == "DOTS") {style = QwtPlotCurve::Dots; penStyle = Qt::DotLine; width = 2.0;} 173 | 174 | for (const auto &curvePair : curves) 175 | { 176 | const auto &curve = curvePair.second; 177 | const auto color = pastelize(getDefaultCurveColor(count++)); 178 | curve->setPen(color, width, penStyle); 179 | curve->setStyle(style); 180 | curve->detach(); 181 | curve->attach(_mainPlot); 182 | _mainPlot->updateChecked(curve.get()); 183 | } 184 | 185 | if (label.isEmpty()) 186 | { 187 | if (curves.size() == 1) 188 | { 189 | curves[0]->setTitle(QString("Ch%1").arg(index)); 190 | } 191 | if (curves.size() == 2) 192 | { 193 | curves[0]->setTitle(QString("Re%1").arg(index)); 194 | curves[1]->setTitle(QString("Im%1").arg(index)); 195 | } 196 | } 197 | else 198 | { 199 | if (curves.size() == 1) 200 | { 201 | curves[0]->setTitle(label); 202 | } 203 | if (curves.size() == 2) 204 | { 205 | curves[0]->setTitle(QString("%1I").arg(label)); 206 | curves[1]->setTitle(QString("%1Q").arg(label)); 207 | } 208 | } 209 | } 210 | 211 | _mainPlot->replot(); 212 | } 213 | 214 | void WaveMonitorDisplay::handleZoomed(const QRectF &rect) 215 | { 216 | //when zoomed all the way out, return to autoscale 217 | if (rect == _mainPlot->zoomer()->zoomBase() and _autoScale) 218 | { 219 | _mainPlot->setAxisAutoScale(QwtPlot::yLeft); 220 | _mainPlot->updateAxes(); //update after axis changes 221 | } 222 | } 223 | 224 | void WaveMonitorDisplay::handleClearChannels(void) 225 | { 226 | _curves.clear(); 227 | _markers.clear(); 228 | _curveCount = 0; 229 | } 230 | 231 | QVariant WaveMonitorDisplay::saveState(void) const 232 | { 233 | return _mainPlot->state(); 234 | } 235 | 236 | void WaveMonitorDisplay::restoreState(const QVariant &state) 237 | { 238 | _mainPlot->setState(state); 239 | } 240 | 241 | void WaveMonitorDisplay::installLegend(void) 242 | { 243 | if (_mainPlot->legend() != nullptr) return; 244 | auto legend = new QwtLegend(_mainPlot); 245 | legend->setDefaultItemMode(QwtLegendData::Checkable); 246 | connect(legend, &QwtLegend::checked, this, &WaveMonitorDisplay::handleLegendChecked); 247 | _mainPlot->insertLegend(legend); 248 | } 249 | 250 | void WaveMonitorDisplay::handleLegendChecked(const QVariant &itemInfo, bool on, int) 251 | { 252 | _mainPlot->infoToItem(itemInfo)->setVisible(on); 253 | _mainPlot->replot(); 254 | 255 | for (const auto &pair : _curves) 256 | { 257 | const bool visible = pair.second.at(0)->isVisible(); 258 | for (const auto &m : _markers[pair.first]) m->setVisible(visible); 259 | } 260 | } 261 | 262 | QwtPlotCurve *WaveMonitorDisplay::getCurve(const size_t index, const size_t which, const size_t width) 263 | { 264 | auto &curves = _curves[index]; 265 | 266 | //detect width change (was complex data, now its real) 267 | if (curves.size() > width) 268 | { 269 | _curveCount -= curves.size(); 270 | curves.clear(); 271 | if (_curveCount <= 1) _mainPlot->insertLegend(nullptr); 272 | } 273 | 274 | auto &curve = curves[which]; 275 | if (not curve) 276 | { 277 | curve.reset(new QwtPlotCurve()); 278 | if (_curveCount++ == 1) this->installLegend(); 279 | this->handleUpdateCurves(); 280 | } 281 | return curve.get(); 282 | } 283 | -------------------------------------------------------------------------------- /WaveMonitor/WaveMonitorDisplay.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2017 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #pragma once 5 | #include //_USE_MATH_DEFINES 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | class PothosPlotter; 16 | class QwtPlotCurve; 17 | class QwtPlotMarker; 18 | 19 | class WaveMonitorDisplay : public QWidget, public Pothos::Block 20 | { 21 | Q_OBJECT 22 | public: 23 | 24 | WaveMonitorDisplay(void); 25 | 26 | ~WaveMonitorDisplay(void); 27 | 28 | QWidget *widget(void) 29 | { 30 | return this; 31 | } 32 | 33 | //! set the plotter's title 34 | void setTitle(const QString &title); 35 | 36 | /*! 37 | * sample rate for the plotter 38 | * controls the time scaling display 39 | */ 40 | void setSampleRate(const double sampleRate); 41 | 42 | void setNumPoints(const size_t numPoints); 43 | 44 | void setAutoScale(const bool autoScale); 45 | 46 | void setYRange(const std::vector &range); 47 | 48 | QString title(void) const; 49 | 50 | size_t numInputs(void) const 51 | { 52 | return this->inputs().size(); 53 | } 54 | 55 | double sampleRate(void) const 56 | { 57 | return _sampleRate; 58 | } 59 | 60 | size_t numPoints(void) const 61 | { 62 | return _numPoints; 63 | } 64 | 65 | void enableXAxis(const bool enb); 66 | void enableYAxis(const bool enb); 67 | void setYAxisTitle(const QString &title); 68 | 69 | void setChannelLabel(const size_t ch, const QString &label) 70 | { 71 | _channelLabels[ch] = label; 72 | QMetaObject::invokeMethod(this, "handleUpdateCurves", Qt::QueuedConnection); 73 | } 74 | 75 | void setChannelStyle(const size_t ch, const std::string &style) 76 | { 77 | _channelStyles[ch] = style; 78 | QMetaObject::invokeMethod(this, "handleUpdateCurves", Qt::QueuedConnection); 79 | } 80 | 81 | void clearChannels(void) 82 | { 83 | QMetaObject::invokeMethod(this, "handleClearChannels", Qt::QueuedConnection); 84 | } 85 | 86 | void setRateLabelId(const std::string &id) 87 | { 88 | _rateLabelId = id; 89 | } 90 | 91 | void work(void); 92 | 93 | //allow for standard resize controls with the default size policy 94 | QSize minimumSizeHint(void) const 95 | { 96 | return QSize(300, 150); 97 | } 98 | QSize sizeHint(void) const 99 | { 100 | return this->minimumSizeHint(); 101 | } 102 | 103 | public slots: 104 | 105 | QVariant saveState(void) const; 106 | 107 | void restoreState(const QVariant &value); 108 | 109 | private slots: 110 | void installLegend(void); 111 | void handleLegendChecked(const QVariant &, bool, int); 112 | void handleSamples(const Pothos::Packet &pkt); 113 | void handleUpdateAxis(void); 114 | void handleUpdateCurves(void); 115 | void handleZoomed(const QRectF &rect); 116 | void handleClearChannels(void); 117 | 118 | private: 119 | QwtPlotCurve *getCurve(const size_t index, const size_t which, const size_t width); 120 | 121 | PothosPlotter *_mainPlot; 122 | double _sampleRate; 123 | double _sampleRateWoAxisUnits; 124 | size_t _numPoints; 125 | bool _autoScale; 126 | std::vector _yRange; 127 | std::string _rateLabelId; 128 | QwtText _triggerMarkerLabel; 129 | 130 | //channel configs 131 | std::map _channelLabels; 132 | std::map _channelStyles; 133 | 134 | //per-port data structs 135 | size_t _curveCount; 136 | std::map>> _curves; 137 | std::map>> _markers; 138 | std::map>> _queueDepth; 139 | }; 140 | -------------------------------------------------------------------------------- /WaveMonitor/WaveMonitorWork.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2016 Josh Blum 2 | // SPDX-License-Identifier: BSL-1.0 3 | 4 | #include "WaveMonitorDisplay.hpp" 5 | #include "PothosPlotter.hpp" 6 | #include "PothosPlotStyler.hpp" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | /*********************************************************************** 14 | * work functions 15 | **********************************************************************/ 16 | void WaveMonitorDisplay::handleSamples(const Pothos::Packet &packet) 17 | { 18 | //extract index 19 | const auto indexIt = packet.metadata.find("index"); 20 | const auto index = (indexIt == packet.metadata.end())?0:indexIt->second.convert(); 21 | if (_queueDepth.at(index)->fetch_sub(1) != 1) return; 22 | 23 | //extract position 24 | const auto positionIt = packet.metadata.find("position"); 25 | const auto position = (positionIt == packet.metadata.end())?0:positionIt->second.convert(); 26 | const auto frac = position-size_t(position); 27 | 28 | //extract level 29 | const auto levelIt = packet.metadata.find("level"); 30 | const auto level = (levelIt == packet.metadata.end())?0:levelIt->second.convert(); 31 | 32 | //extract and convert buffer 33 | const auto &buff = packet.payload; 34 | Pothos::BufferChunk buffI, buffQ; 35 | if (buff.dtype.isComplex()) 36 | { 37 | const auto outs = buff.convertComplex(typeid(float)); 38 | buffI = outs.first; buffQ = outs.second; 39 | const auto sampsI = outs.first.as(); 40 | const auto sampsQ = outs.second.as(); 41 | QVector pointsI(buff.elements()); 42 | QVector pointsQ(buff.elements()); 43 | for (int i = 0; i < pointsI.size(); i++) 44 | { 45 | const auto x = (i-frac)/_sampleRateWoAxisUnits; 46 | pointsI[i] = QPointF(x, sampsI[i]); 47 | pointsQ[i] = QPointF(x, sampsQ[i]); 48 | } 49 | this->getCurve(index, 0, 2)->setSamples(pointsI); 50 | this->getCurve(index, 1, 2)->setSamples(pointsQ); 51 | } 52 | else 53 | { 54 | buffI = buff.convert(typeid(float)); 55 | const auto samps = buffI.as(); 56 | QVector points(buff.elements()); 57 | for (int i = 0; i < points.size(); i++) 58 | { 59 | const auto x = (i-frac)/_sampleRateWoAxisUnits; 60 | points[i] = QPointF(x, samps[i]); 61 | } 62 | this->getCurve(index, 0, 1)->setSamples(points); 63 | } 64 | 65 | //create markers from labels 66 | const bool markerVisible = _curves.at(index).at(0)->isVisible(); 67 | auto &markers = _markers[index]; 68 | markers.clear(); //clear old markers 69 | const auto samps = buffI.as(); 70 | for (const auto &label : packet.labels) 71 | { 72 | auto marker = new QwtPlotMarker(); 73 | marker->setLabel(PothosMarkerLabel(QString::fromStdString(label.id))); 74 | marker->setLabelAlignment(Qt::AlignHCenter); 75 | const auto i = label.index + (label.width-1)/2.0; 76 | marker->setXValue((i-frac)/_sampleRateWoAxisUnits); 77 | marker->setYValue(samps[label.index]); 78 | marker->setVisible(markerVisible); 79 | marker->attach(_mainPlot); 80 | markers.emplace_back(marker); 81 | if (label.id == "T") 82 | { 83 | marker->setLabel(_triggerMarkerLabel); 84 | marker->setXValue(i/_sampleRateWoAxisUnits); 85 | marker->setYValue(level); 86 | } 87 | } 88 | 89 | _mainPlot->replot(); 90 | } 91 | 92 | void WaveMonitorDisplay::work(void) 93 | { 94 | auto inPort = this->input(0); 95 | 96 | if (not inPort->hasMessage()) return; 97 | const auto msg = inPort->popMessage(); 98 | 99 | //label-based messages have in-line commands 100 | if (msg.type() == typeid(Pothos::Label)) 101 | { 102 | const auto &label = msg.convert(); 103 | if (label.id == _rateLabelId and label.data.canConvert(typeid(double))) 104 | { 105 | this->setSampleRate(label.data.convert()); 106 | } 107 | } 108 | 109 | //packet-based messages have payloads to display 110 | if (msg.type() == typeid(Pothos::Packet)) 111 | { 112 | const auto &packet = msg.convert(); 113 | const auto indexIt = packet.metadata.find("index"); 114 | const auto index = (indexIt == packet.metadata.end())?0:indexIt->second.convert(); 115 | 116 | //ensure that we have allocated depth counters (used to avoid displaying old data) 117 | if (not _queueDepth[index]) _queueDepth[index].reset(new std::atomic(0)); 118 | 119 | //send the entire packet into the qt domain for processing 120 | _queueDepth[index]->fetch_add(1); 121 | QMetaObject::invokeMethod(this, "handleSamples", Qt::QueuedConnection, 122 | Q_ARG(Pothos::Packet, packet)); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /cmake/FindQwt.cmake: -------------------------------------------------------------------------------- 1 | # Qt Widgets for Technical Applications 2 | # available at http://www.http://qwt.sourceforge.net/ 3 | # 4 | # The module defines the following variables: 5 | # QWT_FOUND - the system has Qwt 6 | # QWT_INCLUDE_DIR - where to find qwt_plot.h 7 | # QWT_INCLUDE_DIRS - qwt includes 8 | # QWT_LIBRARY - where to find the Qwt library 9 | # QWT_LIBRARIES - aditional libraries 10 | # QWT_MAJOR_VERSION - major version 11 | # QWT_MINOR_VERSION - minor version 12 | # QWT_PATCH_VERSION - patch version 13 | # QWT_VERSION_STRING - version (ex. 5.2.1) 14 | # QWT_ROOT_DIR - root dir (ex. /usr/local) 15 | 16 | #============================================================================= 17 | # Copyright 2010-2013, Julien Schueller 18 | # All rights reserved. 19 | # 20 | # Redistribution and use in source and binary forms, with or without 21 | # modification, are permitted provided that the following conditions are met: 22 | # 23 | # 1. Redistributions of source code must retain the above copyright notice, this 24 | # list of conditions and the following disclaimer. 25 | # 2. Redistributions in binary form must reproduce the above copyright notice, 26 | # this list of conditions and the following disclaimer in the documentation 27 | # and/or other materials provided with the distribution. 28 | # 29 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 30 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 31 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 32 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 33 | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 34 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 35 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 36 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 37 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 38 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 39 | 40 | # The views and conclusions contained in the software and documentation are those 41 | # of the authors and should not be interpreted as representing official policies, 42 | # either expressed or implied, of the FreeBSD Project. 43 | #============================================================================= 44 | 45 | 46 | find_path ( QWT_INCLUDE_DIR 47 | NAMES qwt_plot.h 48 | HINTS ${QT_INCLUDE_DIR} 49 | PATH_SUFFIXES qwt-qt5 qwt 50 | ) 51 | 52 | set ( QWT_INCLUDE_DIRS ${QWT_INCLUDE_DIR} ) 53 | 54 | # version 55 | set ( _VERSION_FILE ${QWT_INCLUDE_DIR}/qwt_global.h ) 56 | if ( EXISTS ${_VERSION_FILE} ) 57 | file ( STRINGS ${_VERSION_FILE} _VERSION_LINE REGEX "define[ ]+QWT_VERSION_STR" ) 58 | if ( _VERSION_LINE ) 59 | string ( REGEX REPLACE ".*define[ ]+QWT_VERSION_STR[ ]+\"(.*)\".*" "\\1" QWT_VERSION_STRING "${_VERSION_LINE}" ) 60 | string ( REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)" "\\1" QWT_MAJOR_VERSION "${QWT_VERSION_STRING}" ) 61 | string ( REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)" "\\2" QWT_MINOR_VERSION "${QWT_VERSION_STRING}" ) 62 | string ( REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)" "\\3" QWT_PATCH_VERSION "${QWT_VERSION_STRING}" ) 63 | endif () 64 | endif () 65 | 66 | 67 | # check version 68 | set ( _QWT_VERSION_MATCH TRUE ) 69 | if ( Qwt_FIND_VERSION AND QWT_VERSION_STRING ) 70 | if ( Qwt_FIND_VERSION_EXACT ) 71 | if ( NOT Qwt_FIND_VERSION VERSION_EQUAL QWT_VERSION_STRING ) 72 | set ( _QWT_VERSION_MATCH FALSE ) 73 | endif () 74 | else () 75 | if ( QWT_VERSION_STRING VERSION_LESS Qwt_FIND_VERSION ) 76 | set ( _QWT_VERSION_MATCH FALSE ) 77 | endif () 78 | endif () 79 | endif () 80 | 81 | 82 | find_library ( QWT_LIBRARY 83 | NAMES qwt-qt5 qwt 84 | HINTS ${QT_LIBRARY_DIR} 85 | ) 86 | 87 | set ( QWT_LIBRARIES ${QWT_LIBRARY} ) 88 | 89 | 90 | # try to guess root dir from include dir 91 | if ( QWT_INCLUDE_DIR ) 92 | string ( REGEX REPLACE "(.*)/include.*" "\\1" QWT_ROOT_DIR ${QWT_INCLUDE_DIR} ) 93 | # try to guess root dir from library dir 94 | elseif ( QWT_LIBRARY ) 95 | string ( REGEX REPLACE "(.*)/lib[/|32|64].*" "\\1" QWT_ROOT_DIR ${QWT_LIBRARY} ) 96 | endif () 97 | 98 | 99 | # handle the QUIETLY and REQUIRED arguments 100 | include ( FindPackageHandleStandardArgs ) 101 | if ( CMAKE_VERSION LESS 2.8.3 ) 102 | find_package_handle_standard_args( Qwt DEFAULT_MSG QWT_LIBRARY QWT_INCLUDE_DIR _QWT_VERSION_MATCH ) 103 | else () 104 | find_package_handle_standard_args( Qwt REQUIRED_VARS QWT_LIBRARY QWT_INCLUDE_DIR _QWT_VERSION_MATCH VERSION_VAR QWT_VERSION_STRING ) 105 | endif () 106 | 107 | 108 | mark_as_advanced ( 109 | QWT_LIBRARY 110 | QWT_LIBRARIES 111 | QWT_INCLUDE_DIR 112 | QWT_INCLUDE_DIRS 113 | QWT_MAJOR_VERSION 114 | QWT_MINOR_VERSION 115 | QWT_PATCH_VERSION 116 | QWT_VERSION_STRING 117 | QWT_ROOT_DIR 118 | ) 119 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | pothos-plotters (0.4.2-1) unstable; urgency=low 2 | 3 | * Release 0.4.2 (2021-07-25) 4 | 5 | -- Josh Blum Sun, 25 Jul 2021 17:49:34 -0000 6 | 7 | pothos-plotters (0.4.1-1) unstable; urgency=low 8 | 9 | * Release 0.4.1 (2018-04-24) 10 | 11 | -- Josh Blum Tue, 24 Apr 2018 20:30:21 -0000 12 | 13 | pothos-plotters (0.4.0-1) unstable; urgency=low 14 | 15 | * Release 0.4.0 (2017-12-25) 16 | 17 | -- Josh Blum Mon, 25 Dec 2017 11:32:08 -0000 18 | 19 | pothos-plotters (0.3.1-1) unstable; urgency=low 20 | 21 | * Release 0.3.1 (2017-06-22) 22 | 23 | -- Josh Blum Thu, 22 Jun 2017 19:53:40 -0000 24 | 25 | pothos-plotters (0.3.0-1) unstable; urgency=low 26 | 27 | * Release 0.3.0 (2017-04-27) 28 | 29 | -- Josh Blum Thu, 27 Apr 2017 17:00:27 -0000 30 | 31 | pothos-plotters (0.2.2-1) unstable; urgency=low 32 | 33 | * Release 0.2.2 (2017-04-23) 34 | 35 | -- Josh Blum Sun, 23 Apr 2017 20:38:10 -0000 36 | 37 | pothos-plotters (0.2.1-ppa1) unstable; urgency=low 38 | 39 | * Release 0.2.1 (2016-09-24) 40 | 41 | -- Josh Blum Sun, 25 Sep 2016 09:29:56 -0700 42 | 43 | pothos-plotters (0.2.0) unstable; urgency=low 44 | 45 | * Release 0.2.0 (2016-08-02) 46 | 47 | -- Josh Blum Tue, 02 Aug 2016 14:27:00 -0700 48 | 49 | pothos-plotters (0.1.1) unstable; urgency=low 50 | 51 | * Release 0.1.1 (2016-05-10) 52 | 53 | -- Josh Blum Tue, 10 May 2016 16:22:03 -0700 54 | 55 | pothos-plotters (0.1.0) unstable; urgency=low 56 | 57 | * Release 0.1.0 (2015-12-16) 58 | 59 | -- Josh Blum Wed, 16 Dec 2015 16:30:45 -0800 60 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: pothos-plotters 2 | Section: science 3 | Priority: optional 4 | Maintainer: Josh Blum 5 | Build-Depends: 6 | debhelper (>= 9.0.0), 7 | cmake (>= 2.8.9), 8 | libpoco-dev (>= 1.6), 9 | libpothos-dev, 10 | libspuce-dev, 11 | qtbase5-dev, libqt5svg5-dev, libqt5opengl5-dev, 12 | libqwt-qt5-dev 13 | Standards-Version: 4.5.0 14 | Homepage: https://github.com/pothosware/PothosPlotters/wiki 15 | Vcs-Git: https://github.com/pothosware/PothosPlotters.git 16 | Vcs-Browser: https://github.com/pothosware/PothosPlotters 17 | 18 | Package: pothos-modules-plotters 19 | Section: libs 20 | Architecture: all 21 | Pre-Depends: ${misc:Pre-Depends} 22 | Depends: pothos0.7-modules-plotters, ${misc:Depends} 23 | Description: graphical plotter widgets - metapackage 24 | The Pothos data-flow software suite. 25 | 26 | Package: pothos0.7-modules-plotters 27 | Section: libs 28 | Architecture: any 29 | Multi-Arch: same 30 | Pre-Depends: ${misc:Pre-Depends} 31 | Depends: libpothosplotterutils0.2.0 (= ${binary:Version}), ${shlibs:Depends}, ${misc:Depends} 32 | Description: graphical plotter widgets 33 | The Pothos data-flow software suite. 34 | 35 | Package: libpothosplotterutils0.2.0 36 | Section: libs 37 | Architecture: any 38 | Multi-Arch: same 39 | Pre-Depends: ${misc:Pre-Depends} 40 | Depends: ${shlibs:Depends}, ${misc:Depends} 41 | Description: plotter utility shared library 42 | The Pothos data-flow software suite. 43 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: pothos-plotters 3 | Source: https://github.com/pothosware/PothosPlotters/wiki 4 | 5 | Files: * 6 | Copyright: 7 | Copyright (c) 2014-2021 Josh Blum 8 | License: BSL-1.0 9 | Boost Software License - Version 1.0 - August 17th, 2003 10 | . 11 | Permission is hereby granted, free of charge, to any person or organization 12 | obtaining a copy of the software and accompanying documentation covered by 13 | this license (the "Software") to use, reproduce, display, distribute, 14 | execute, and transmit the Software, and to prepare derivative works of the 15 | Software, and to permit third-parties to whom the Software is furnished to 16 | do so, all subject to the following: 17 | . 18 | The copyright notices in the Software and this entire statement, including 19 | the above license grant, this restriction and the following disclaimer, 20 | must be included in all copies of the Software, in whole or in part, and 21 | all derivative works of the Software, unless such copies or derivative 22 | works are solely in the form of machine-executable object code generated by 23 | a source language processor. 24 | . 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 28 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 29 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 30 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 31 | DEALINGS IN THE SOFTWARE. 32 | -------------------------------------------------------------------------------- /debian/docs: -------------------------------------------------------------------------------- 1 | README.md 2 | -------------------------------------------------------------------------------- /debian/libpothosplotterutils0.2.0.install: -------------------------------------------------------------------------------- 1 | usr/lib/*/libPothosPlotterUtils.so.* 2 | -------------------------------------------------------------------------------- /debian/pothos-plotters.install: -------------------------------------------------------------------------------- 1 | usr/lib/*/Pothos/modules/plotters/ 2 | -------------------------------------------------------------------------------- /debian/pothos0.7-modules-plotters.install: -------------------------------------------------------------------------------- 1 | usr/lib/*/Pothos/modules* 2 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -*- makefile -*- 3 | 4 | # extract the architecture for setting the library path suffix 5 | DEB_HOST_MULTIARCH ?= $(shell dpkg-architecture -qDEB_HOST_MULTIARCH) 6 | 7 | # Uncomment this to turn on verbose mode. 8 | #export DH_VERBOSE=1 9 | 10 | %: 11 | dh $@ --buildsystem=cmake --parallel 12 | 13 | override_dh_auto_configure: 14 | dh_auto_configure -- \ 15 | -DLIB_SUFFIX="/$(DEB_HOST_MULTIARCH)" \ 16 | -DCMAKE_SKIP_RPATH=TRUE 17 | 18 | override_dh_installchangelogs: 19 | dh_installchangelogs Changelog.txt 20 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | --------------------------------------------------------------------------------