diff --git a/tools/ecmcFFTMainGui.py b/tools/ecmcFFTMainGui.py new file mode 100644 index 0000000..9f8741d --- /dev/null +++ b/tools/ecmcFFTMainGui.py @@ -0,0 +1,342 @@ +#************************************************************************* +# Copyright (c) 2020 European Spallation Source ERIC +# ecmc is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +# +# ecmcFFTMainGui.py +# +# Created on: October 6, 2020 +# Author: Anders Sandström +# +# Plots two waveforms (x vs y) updates for each callback on the y-pv +# +#************************************************************************* + +import sys +import epics +from PyQt5.QtWidgets import * +from PyQt5 import QtWidgets +from PyQt5.QtCore import * +from PyQt5.QtGui import * +import numpy as np +import matplotlib +matplotlib.use("Qt5Agg") +from matplotlib.figure import Figure +from matplotlib.animation import TimedAnimation +from matplotlib.lines import Line2D +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas +from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar +import matplotlib.pyplot as plt +import threading + +# FFT object pvs Plugin-FFT- +# IOC_TEST:Plugin-FFT0-stat +# IOC_TEST:Plugin-FFT0-NFFT +# IOC_TEST:Plugin-FFT0-Mode-RB +# IOC_TEST:Plugin-FFT0-SampleRate-Act x +# IOC_TEST:Plugin-FFT0-Enable x +# IOC_TEST:Plugin-FFT0-Trigg x +# IOC_TEST:Plugin-FFT0-Source x +# IOC_TEST:Plugin-FFT0-Raw-Data-Act x +# IOC_TEST:Plugin-FFT0-PreProc-Data-Act +# IOC_TEST:Plugin-FFT0-Spectrum-Amp-Act x +# IOC_TEST:Plugin-FFT0-Spectrum-X-Axis-Act x + + +class comSignal(QObject): + data_signal = pyqtSignal(object) + +class ecmcFFTMainGui(QtWidgets.QDialog): + def __init__(self,prefix=None,fftPluginId=None): + super(ecmcFFTMainGui, self).__init__() + self.pvPrefixStr = prefix + self.fftPluginId = fftPluginId + + # Callbacks through signals + self.comSignalSpectX = comSignal() + self.comSignalSpectX.data_signal.connect(self.callbackFuncSpectX) + self.comSignalSpectY = comSignal() + self.comSignalSpectY.data_signal.connect(self.callbackFuncSpectY) + self.comSignalRawData = comSignal() + self.comSignalRawData.data_signal.connect(self.callbackFuncrawData) + + self.pause = 0 + + # Data Arrays + self.spectX = None + self.spectY = None + self.rawdata = None + self.enable = None + + self.figure = plt.figure() + self.plottedLineSpect = None + self.plottedLineRaw = None + self.axSpect = None + self.axRaw = None + self.canvas = FigureCanvas(self.figure) + self.toolbar = NavigationToolbar(self.canvas, self) + self.pauseBtn = QPushButton(text = 'pause') + self.pauseBtn.setFixedSize(100, 50) + self.pauseBtn.clicked.connect(self.pauseBtnAction) + self.pauseBtn.setStyleSheet("background-color: green") + + self.enableBtn = QPushButton(text = 'enable FFT') + self.enableBtn.setFixedSize(100, 50) + self.enableBtn.clicked.connect(self.enableBtnAction) + + self.triggBtn = QPushButton(text = 'trigg FFT') + self.triggBtn.setFixedSize(100, 50) + self.triggBtn.clicked.connect(self.triggBtnAction) + + # Pv names based on structure: Plugin-FFT- + self.pvNameSpectY = self.buildPvName('Spectrum-Amp-Act') # "IOC_TEST:Plugin-FFT1-Spectrum-Amp-Act" + print("self.pvNameSpectY=" + self.pvNameSpectY) + self.pvNameSpectX = self.buildPvName('Spectrum-X-Axis-Act') # "IOC_TEST:Plugin-FFT1-Spectrum-X-Axis-Act" + print("self.pvNameSpectX=" + self.pvNameSpectX) + self.pvNameRawDataY = self.buildPvName('Raw-Data-Act') # IOC_TEST:Plugin-FFT0-Raw-Data-Act + print("self.pvNameRawDataY=" + self.pvNameRawDataY) + self.pvnNameEnable = self.buildPvName('Enable') # IOC_TEST:Plugin-FFT0-Enable + print("self.pvnNameEnable=" + self.pvnNameEnable) + self.pvnNameTrigg = self.buildPvName('Trigg') # IOC_TEST:Plugin-FFT0-Trigg + print("self.pvnNameTrigg=" + self.pvnNameTrigg) + self.pvnNameSource = self.buildPvName('Source') # IOC_TEST:Plugin-FFT0-Source + print("self.pvnNameSource=" + self.pvnNameSource) + self.pvnNameSampleRate = self.buildPvName('SampleRate-Act') # IOC_TEST:Plugin-FFT0-SampleRate-Act + print("self.pvnNameSampleRate=" + self.pvnNameSampleRate) + self.pvnNameNFFT = self.buildPvName('NFFT') # IOC_TEST:Plugin-FFT0-NFFT + print("self.pvnNameNFFT=" + self.pvnNameNFFT) + + self.connectPvs() + + # Check actual value of pvs + if(self.pvEnable.get()>0): + self.enableBtn.setStyleSheet("background-color: green") + self.enable = True + else: + self.enableBtn.setStyleSheet("background-color: red") + self.enable = False + + self.sourceStr = self.pvSource.get(as_string=True) + self.sampleRate = self.pvSampleRate.get() + self.NFFT = self.pvNFFT.get() + + + # Fix layout + self.setGeometry(300, 300, 900, 700) + self.setWindowTitle("ecmc FFT Main plot: prefix=" + self.pvPrefixStr + " , fftId=" + str(self.fftPluginId) +", source=" + self.sourceStr + ", rate=" + str(self.sampleRate) + ", NFFT=" + str(self.NFFT) ) + layoutVert = QVBoxLayout() + layoutVert.addWidget(self.toolbar) + layoutVert.addWidget(self.canvas) + + layoutControl = QHBoxLayout() + layoutControl.addWidget(self.pauseBtn) + layoutControl.addWidget(self.enableBtn) + layoutControl.addWidget(self.triggBtn) + + frameControl = QFrame(self) + frameControl.setFixedHeight(70) + frameControl.setLayout(layoutControl) + + layoutVert.addWidget(frameControl) + + self.setLayout(layoutVert) + return + + def buildPvName(self, suffixname): + return self.pvPrefixStr + 'Plugin-FFT' + str(self.fftPluginId) + '-' + suffixname + + def connectPvs(self): + + if self.pvNameSpectX is None: + raise RuntimeError("pvname X spect must not be 'None'") + if len(self.pvNameSpectX)==0: + raise RuntimeError("pvname X spect must not be ''") + + if self.pvNameSpectY is None: + raise RuntimeError("pvname y spect must not be 'None'") + if len(self.pvNameSpectY)==0: + raise RuntimeError("pvname y spect must not be ''") + + if self.pvNameRawDataY is None: + raise RuntimeError("pvname raw data must not be 'None'") + if len(self.pvNameRawDataY)==0: + raise RuntimeError("pvname raw data must not be ''") + + if self.pvnNameEnable is None: + raise RuntimeError("pvname enable must not be 'None'") + if len(self.pvnNameEnable)==0: + raise RuntimeError("pvname enable must not be ''") + + if self.pvnNameTrigg is None: + raise RuntimeError("pvname trigg must not be 'None'") + if len(self.pvnNameTrigg)==0: + raise RuntimeError("pvname trigg must not be ''") + + if self.pvnNameSource is None: + raise RuntimeError("pvname source must not be 'None'") + if len(self.pvnNameSource)==0: + raise RuntimeError("pvname source must not be ''") + + if self.pvnNameSampleRate is None: + raise RuntimeError("pvname sample rate must not be 'None'") + if len(self.pvnNameSampleRate)==0: + raise RuntimeError("pvname sample rate must not be ''") + + if self.pvnNameNFFT is None: + raise RuntimeError("pvname NFFT must not be 'None'") + if len(self.pvnNameNFFT)==0: + raise RuntimeError("pvname NFFT must not be ''") + + self.pvSpectX = epics.PV(self.pvNameSpectX) + #print('self.pvSpectX: ' + self.pvSpectX.info) + + self.pvSpectY = epics.PV(self.pvNameSpectY) + #print('self.pvSpectY: ' + self.pvSpectY.info) + + self.pvRawData = epics.PV(self.pvNameRawDataY) + #print('self.pvRawData: ' + self.pvSpectY.info) + + self.pvEnable = epics.PV(self.pvnNameEnable) + #print('self.pvEnable: ' + self.pvEnable.info) + + self.pvTrigg = epics.PV(self.pvnNameTrigg) + #print('self.pvTrigg: ' + self.pvTrigg.info) + + self.pvSource = epics.PV(self.pvnNameSource) + #print('self.pvSource: ' + self.pvSource.info) + + self.pvSampleRate = epics.PV(self.pvnNameSampleRate) + #print('self.pvSampleRate: ' + self.pvSampleRate.info) + + self.pvNFFT = epics.PV(self.pvnNameNFFT) + #print('self.pvNFFT: ' + self.pvNFFT.info) + + self.pvSpectX.add_callback(self.onChangePvSpectX) + self.pvSpectY.add_callback(self.onChangePvSpectY) + self.pvRawData.add_callback(self.onChangePvrawData) + + QCoreApplication.processEvents() + + def onChangePvSpectX(self,pvname=None, value=None, char_value=None,timestamp=None, **kw): + self.comSignalSpectX.data_signal.emit(value) + + def onChangePvSpectY(self,pvname=None, value=None, char_value=None,timestamp=None, **kw): + self.comSignalSpectY.data_signal.emit(value) + + def onChangePvrawData(self,pvname=None, value=None, char_value=None,timestamp=None, **kw): + self.comSignalRawData.data_signal.emit(value) + + def pauseBtnAction(self): + self.pause = not self.pause + if self.pause: + self.pauseBtn.setStyleSheet("background-color: red") + else: + self.pauseBtn.setStyleSheet("background-color: green") + # Retrigger plots with newest values + self.comSignalSpectY.data_signal.emit(self.spectY) + self.comSignalRawData.data_signal.emit(self.rawdata) + return + + def enableBtnAction(self): + self.enable = not self.enable + self.pvEnable.put(self.enable) + if self.enable: + self.enableBtn.setStyleSheet("background-color: green") + else: + self.enableBtn.setStyleSheet("background-color: red") + return + + def triggBtnAction(self): + self.pvTrigg.put(True) + return + + def callbackFuncSpectX(self, value): + if(np.size(value)) > 0: + self.spectX = value + self.xDataValid = 1 + return + + def callbackFuncSpectY(self, value): + if(np.size(value)) > 0: + self.spectY = value + self.plotSpect() + return + + def callbackFuncrawData(self, value): + if(np.size(value)) > 0: + self.rawdata = value + self.plotRaw() + return + + def plotSpect(self): + if self.pause: + return + if self.spectX is None: + return + if self.spectY is None: + return + + print("plotSpect") + # create an axis for spectrum + if self.axSpect is None: + self.axSpect = self.figure.add_subplot(212) + + # plot data + if self.plottedLineSpect is not None: + self.plottedLineSpect.remove() + + self.plottedLineSpect, = self.axSpect.plot(self.spectX,self.spectY, 'b*-') + self.axSpect.grid(True) + + self.axSpect.set_xlabel(self.pvNameSpectX +' [' + self.pvSpectX.units + ']') + self.axSpect.set_ylabel(self.pvNameSpectY +' [' + self.pvSpectY.units + ']') + + # refresh canvas + self.canvas.draw() + + self.axSpect.autoscale(enable=False) + + def plotRaw(self): + if self.pause: + return + if self.rawdata is None: + return + + # create an axis for spectrum + if self.axRaw is None: + self.axRaw = self.figure.add_subplot(211) + + # plot data + if self.plottedLineRaw is not None: + self.plottedLineRaw.remove() + + self.plottedLineRaw, = self.axRaw.plot(self.rawdata, 'b*-') + self.axRaw.grid(True) + + self.axRaw.set_xlabel('Time []') + self.axRaw.set_ylabel(self.pvNameRawDataY +' [' + self.pvRawData.units + ']') + + # refresh canvas + self.canvas.draw() + + self.axRaw.autoscale(enable=True) + +def printOutHelp(): + print("ecmcFFTMainGui: Plots waveforms of FFT data (updates on Y data callback). ") + print("python ecmcFFTMainGui.py ") + print(": Ioc prefix ('IOC_TEST:')") + print(" : Id of fft plugin ('0')") + print("example : python ecmcFFTMainGui.py 'IOC_TEST:' '0'") + print("Will connect to Pvs: Plugin-FFT-*") + +if __name__ == "__main__": + import sys + if len(sys.argv)!=3: + printOutHelp() + sys.exit() + prefix=sys.argv[1] + fftid=int(sys.argv[2]) + app = QtWidgets.QApplication(sys.argv) + window=ecmcFFTMainGui(prefix=prefix,fftPluginId=fftid) + window.show() + sys.exit(app.exec_())