Add python GUI WIP

This commit is contained in:
Anders Sandstrom
2023-08-04 19:59:18 +02:00
parent b5f5cee45e
commit 9dc4adcf8e
3 changed files with 866 additions and 458 deletions

449
README.md
View File

@@ -1,453 +1,10 @@
e3-ecmc_plugin_fft
ecmc_plugin_motion
======
ESS Site-specific EPICS module : ecmcPlugin_FFT
Plugin designed for commisioning and troubleshooting of motion axes.
Motion data are sampled, buffered and exposed to epics as waveforms.
A shared library with FFT functionalities loadable into ecmc:
https://github.com/epics-modules/ecmc (or local ess fork https://github.com/icshwi/ecmc).
Configuration is made through ecmccfg:
https://github.com/paulscherrerinstitute/ecmccfg (ot local ess fork https://github.com/icshwi/ecmccfg)
FFT:s are calculated with the kissfft lib:
https://github.com/mborgerding/kissfft
# Introduction
The main functionality of this plugin is to make FFT analysis of ecmc data. Most data in ecmc is accessible:
1. EtherCAT data ("ec0.s1.AI_1" or "ec0.s2.mm.CH1_ARRAY")
2. Motion data ("ax1.actpos")
3. PLC data ("plcs.plc0.static.testvalue")
Precautions have been taken in order to disturb ecmc real time thread as little as possible:
1. All CPU intensive calculations are handled in a low prio work thread (not ecmc thread).
2. Communication to epics is made via a dedicated asynPortDriver (not the same that ecmc uses).
See below for configuration options and the different modes supported by this ecmc plugin.
## Loading of FFT plugin in ecmc:
A plugin is loaded by the ecmccfg command loadPlugin:
https://github.com/icshwi/ecmccfg/blob/master/scripts/loadPlugin.cmd
Example:
```
${SCRIPTEXEC} ${ecmccfg_DIR}loadPlugin.cmd, "PLUGIN_ID=0,FILE=libecmcPlugin_FFT.so,CONFIG='DBG_PRINT=1;SOURCE=ax1.actpos;RM_LIN=1;', REPORT=1
dbLoadRecords(ecmcPluginFFT.template,"P=$(IOC):,INDEX=0, NELM=${FFT_NELM}")
```
This plugin supports multiple loading. For each load of the plugin a new FFT object will be created. In order to access these plugins, from plc:s or EPICS records, they can be accessed by an index. The first FFT plugin will have index 0. The next loaded FFT plugin will have index 1...
Note: If another plugin is loaded in between the loading of FFT plugins, it will have no affect on these FFT indexes (so the FFT index is _not_ the same as plugin index).
## Configuration
The different available configuration settings:
* SOURCE= source variable : Sets source variable for FFT (example: ec0.s1.AI_1). This config is mandatory.
* DBG_PRINT=1/0 : Enables/disables printouts from plugin, default = disabled.
* NFFT= nfft : Data points to collect, default = 4096.
* SCALE=scale : Apply scale to input data, default = 1.0.
* RM_DC=1/0 : Remove DC offset of input data (SOURCE), default = disabled.
* RM_LIN=1/0 : Remove linear component input data (SOURCE), default = disabled.
* ENABLE=1/0 : Enable data acq. and calcs (can be controlled over asyn), default = disabled.
* MODE=CONT/TRIGG : Continious or triggered mode, defaults to TRIGG
* RATE=rate in hz : fft data sample rate in hz (must be lower than ecmc rate and (ecmc_rate/fft_rate)=integer), default = ecmc rate.
* BREAKTABLE= EPICS breaktable : Apply breaktable to raw value.
Example configuration string:
```
"SOURCE=ax1.poserr;MODE=TRIGG;DBG_PRINT=1;ENABLE=1;"
```
#### SOURCE (mandatory)
The data source is defined by setting the SOURCE option in the plugin configuration string.
This configuration is mandatory.
Example: Axis 1 actpos (See RM_LIN config below)
```
"DBG_PRINT=1;SOURCE=ax1.actpos;RM_LIN=1;"
```
Example: Ethercat slave 1 analog input ch1
```
"DBG_PRINT=1;SOURCE=ec0.s1.AI_1;"
```
#### DBG_PRINT (default: disabled)
Enable/disable printouts from plugin can be made bu setting the "DBG_PRINT" option.
Example: Disable
```
"DBG_PRINT=0;SOURCE=ax1.poserr;"
```
#### NFFT (default: 4096)
Defines number of samples for each measurement.
Note: Must be a n² number..
Example: 1024
```
"NFFT=1024;DBG_PRINT=0;SOURCE=ax1.poserr;"
```
#### SCALE (default 1.0)
Apply custom scale to input data.
Example: 5.0
```
"SCALE=5.0;NFFT=1024;DBG_PRINT=0;SOURCE=ax1.poserr;"
```
### RM_DC
Remove DC of input signal. Default is disabled.
Example: Remove DC offset
```
"RM_DC=1;SCALE=1;NFFT=1024;DBG_PRINT=0;SOURCE=ax1.poserr;"
```
### RM_LIN
Remove linear component of input signal. Default is disabled.
The linear component is calculated by least square method.
Could be usefull for values that increase, like actual position.
Example: Remove linear component
```
"RM_LIN=1;SCALE=1;NFFT=1024;DBG_PRINT=0;SOURCE=ax1.poserr;"
```
#### ENABLE (default: disabled)
Enable data acq. and FFT calcs. The default settings is disabled so needs to be enabled from plc or over asyn in order to start calculations.
Example: Enable at startup by config
```
"ENABLE=1;RM_DC=1;SCALE=1;NFFT=1024;DBG_PRINT=0;SOURCE=ax1.poserr;"
```
Example: Enable FFT index 0 from EPICS:
```
caput IOC_TEST:Plugin-FFT0-Enable 1
```
Example: Enable FFT index 0 from ecmc PLC code:
```
fft_enable(0,1)
```
#### MODE (default: TRIGG)
The FFT module can operate in two different modes:
* CONTINIOUS (CONT)
* TRIGGERED (TRIGG)
Continious mode:
1. Clear data buffers
2. Wait for enable (from plc or asyn/epics record or configuration(see above))
3. Data acqusition starts
4. When buffers are full a worker thread will be triggered to calculate the FFT.
5. FTT results are sent by callback over asyn to epics records
6. goto 1.
Note: data will be "lost" during calculation time.
Triggered mode:
1. Clear data buffers
2. Wait for enable (from plc or asyn/epics record or configuration(see above))
3. Wait for trigger (from plc or asyn/epics record)
3. Data acqusition starts
4. When buffers are full a worker thread will be triggered to calculate the FFT.
5. FTT results are sent by callback over asyn to epics records
6. goto 1.
Example: Mode triggered
```
"MODE=TRIGG;ENABLE=1;RM_DC=1;SCALE=1;NFFT=1024;DBG_PRINT=0;SOURCE=ax1.poserr;"
```
Example: Mode from EPICS record
```
# CONT
caput IOC_TEST:Plugin-FFT0-Mode-RB 1
# TRIGG
caput IOC_TEST:Plugin-FFT0-Mode-RB 2
```
Note: The record is a output record with readback so can both be read and written to.
#### RATE (default: the ecmc rate of the selected data source)
Sets the sample rate of the raw input data (from data source). The default value is the ecmc rate for that data source.
Note: only a lower and "integer" division of sample rate can be defined.
Example: Rate = 100Hz
```
"RATE=100;MODE=TRIGG;ENABLE=1;RM_DC=1;SCALE=1;NFFT=1024;DBG_PRINT=0;SOURCE=ax1.poserr;"
```
#### BREAKTABLE (default: no break table)
Note: The break table must be added in EPICS
Example: Apply BREAKTABLE=typeTelemess33020_21877 to raw value
```
"...;BREAKTABLE=typeTelemess33020_21877;"
```
Example: Load custom breaktable in EPICS
```
dbLoadRecords ./bptBreakTable.dbd
```
Example: breaktable (in a bptBreakTable.dbd)
```
# <raw> <eng>
breaktable(typeTelemess33020_21877) {
2502.48 0
4058.89 1.5
6164.64 3
8880.74 4.5
11993.57 6
15259.00 7.5
18371.84 9
21271.05 10.5
24658.54 12
27344.13 13.5
29297.28 15
}
```
## EPICS records
Each FFT plugin object will create a new asynportdriver-port named "PLUGIN.FFT<index>" (index is explaine above).
The reason for a dedicated asynport is to disturb ecmc as little as possible.
The plugin contains a template file, "ecmcPluginFFT.template", that will make most information availbe from records:
* Rawdata array (ro)
* FFT amplitude array (result) (ro)
* FFT x axis (frequencies) (ro)
* Status (ro)
* Mode (rw)
* Sample rate (ro)
* Enable cmd (rw)
* Trigger cmd (rw)
* NFFT (ro)
The available records from this template file can be listed by the cmd (here two FFT plugins loaded):
```
dbgrep *FFT*
IOC_TEST:Plugin-FFT0-SampleRate-Act
IOC_TEST:Plugin-FFT1-SampleRate-Act
IOC_TEST:Plugin-FFT0-Mode-RB
IOC_TEST:Plugin-FFT1-Mode-RB
IOC_TEST:Plugin-FFT0-Source
IOC_TEST:Plugin-FFT0-Raw-Data-Act
IOC_TEST:Plugin-FFT0-Spectrum-Amp-Act
IOC_TEST:Plugin-FFT0-Spectrum-X-Axis-Act
IOC_TEST:Plugin-FFT1-Source
IOC_TEST:Plugin-FFT1-Raw-Data-Act
IOC_TEST:Plugin-FFT1-Spectrum-Amp-Act
IOC_TEST:Plugin-FFT1-Spectrum-X-Axis-Act
IOC_TEST:Plugin-FFT0-Enable
IOC_TEST:Plugin-FFT0-Trigg
IOC_TEST:Plugin-FFT1-Enable
IOC_TEST:Plugin-FFT1-Trigg
IOC_TEST:Plugin-FFT0-stat
IOC_TEST:Plugin-FFT0-NFFT
IOC_TEST:Plugin-FFT1-stat
IOC_TEST:Plugin-FFT1-NFFT
```
Note: The FFT asynparameters will not be visible by the ecmcReport iocsh command since the FFT records belong to another port.
## PLC interface
### PLC Functions
1. "fft_clear(arg0);" double fft_clear(index) : Clear/resets all buffers fft[index].
2. "fft_enable(arg0, arg1);" double fft_enable(index, enable) : Set enable for fft[index].
3. "fft_trigg(arg0);" double fft_trigg(index) : Trigg new measurement for fft[index]. Will clear buffers.
4. "fft_mode(arg0, arg1);" double fft_mode(index, mode) : Set mode Cont(1)/Trigg(2) for fft[index].
5. "fft_stat(arg0);" double fft_stat(index) : Get status of fft (NO_STAT, IDLE, ACQ, CALC) for fft[index].
### PLC Constants:
These constants are for use togheter with the above listed PLC functions:
1. "fft_CONT" = 1: FFT Mode: Continious
2. "fft_TRIGG" = 2: FFT Mode :Triggered
3. "fft_NO_STAT" = 0: FFT Status: Invalid state
4. "fft_IDLE" = 1: FFT Status: Idle state (waiting for trigger)
5. "fft_ACQ" = 2: FFT Status: Acquiring data
6. "fft_CALC" = 3: FFT Status: Calculating result
## Example script
An example script can be found in the iocsh directory of this repo.
This example load 2 FFT plugin objects:
1. SOURCE="plcs.plc0.static.sineval" which is a plc variable updated as a sinus with default freq 5 Hz.
2. SOURCE="ecmc.thread.latency.max". This is the execution latency of ecmc (in nanoseconds).
Start one FFT calc over EPICS records:
```
# Enable
caput IOC_TEST:Plugin-FFT0-Enable 1
# Trigg a new calc
caput IOC_TEST:Plugin-FFT1-Trigg 1
```
#### Example input signal
Epics record: IOC_TEST:Plugin-FFT0-Raw-Data-Act
|![ecmc dep](docs/ecmcPLC5HzRAW.png)|
| :---: |
|**Figure 1** Raw data. |
#### Example FFT result
Epics records:
* X = IOC_TEST:Plugin-FFT0-Spectrum-X-Axis-Act
* Y = IOC_TEST:Plugin-FFT0-Spectrum-Amp-Act
|![ecmc dep](docs/ecmcPLC5HzFFT.png)|
| :---: |
|**Figure 2** Resulting FFT amplitude. |
#### ecmc PLC code for example:
```
static.time:=ec_get_time()/1E9;
static.sineval:=sin(2*pi*${FREQ=5}*static.time);
```
## FFT GUI
### FFT GUI (FFT and rawdata plots + controls)
A simple tool, [ecmcFFTMainGui.py](tools/ecmcFFTMainGui.py), to visualize the calculated spectrum, rawdata and also plugin controls can be found in the tools directory. The GUI connects to the plugin records over pypics framwork.
The gui are included in the ecmccomgui repo:
https://github.com/anderssandstrom/ecmccomgui
Example: ecmcFFTMainGui.py help printout
```
python ecmcFFTMainGui.py
ecmcFFTMainGui: Plots waveforms of FFT data (updates on Y data callback).
python ecmcFFTMainGui.py <prefix> <fftId>
<prefix>: Ioc prefix ('IOC_TEST:')
<fftId> : Id of fft plugin ('0')
example : python ecmcFFTMainGui.py 'IOC_TEST:' '0'
Will connect to Pvs: <prefix>Plugin-FFT<fftId>-*
```
Example: Start ecmcFFMainTGui.py for:
* predix="IOC_TEST:"
* fftPluginId=0 (the first loaded FFT plugin in the ioc)
```
python ecmcFFTMainGui.py IOC_TEST: 0
```
![ecmcFFTMainGui.py](docs/gui/ecmcFFTMainGui.png)
### FFT GUI (only FFT plot)
A simple tool, [ecmcFFTGui.py](tools/ecmcFFTGui.py), to visualize the calculated spectrum can be found in the tools directory.
The GUI connects to the plugin records over pypics framwork.
Example: ecmcFFTGui.py help printout
```
python ecmcFFTGui.py
ecmcFFTGui: Plots waveforms of FFT data (updates on Y data callback).
python ecmcFFTGui.py <x.pv> <y.pv>
example: python ecmcFFTGui.py IOC_TEST:Plugin-FFT1-Spectrum-X-Axis-Act IOC_TEST:Plugin-FFT1-Spectrum-Amp-Act
```
Example: Start ecmcFFTGui.py for spectrum amplitude and freq. waveform pvs
```
python ecmcFFTGui.py IOC_TEST:Plugin-FFT1-Spectrum-X-Axis-Act IOC_TEST:Plugin-FFT1-Spectrum-Amp-Act
```
![ecmcFFTMainGui.py](docs/gui/ecmcFFTGui.png)
### Array GUI (generic waveform plt)
A simple generic tool, [ecmcFFTGui.py](tools/ecmcFFTGui.py), to visualize wavforms.
The GUI connects to the plugin records over pypics framwork.
Example: ecmcArrayGui.py help printout
```
python ecmcArrayGui.py
ecmcArrayGui: Plots waveforms data (updates on data callback).
python ecmcArrayGui.py <y.pv>
example: python ecmcArrayGui.py IOC_TEST:Plugin-FFT0-Raw-Data-Act
```
Example: Start ecmcArrayGui.py for raw data wavform
```
python ecmcArrayGui.py IOC_TEST:Plugin-FFT0-Raw-Data-Act
```
![ecmcFFTMainGui.py](docs/gui/ecmcArrayGui.png)
### Needed packages:
* python 3.5
* epics
* PyQt5
* numpy
* matplotlib
## Plugin info
```
Plugin info:
Index = 1
Name = ecmcPlugin_FFT
Description = FFT plugin for use with ecmc.
Option description =
DBG_PRINT=<1/0> : Enables/disables printouts from plugin, default = disabled.
SOURCE=<source> : Sets source variable for FFT (example: ec0.s1.AI_1).
NFFT=<nfft> : Data points to collect, default = 4096.
SCALE=<1/0> : Apply scale, default = disabled.
RM_DC=<1/0> : Remove DC offset of input data (SOURCE), default = disabled.
ENABLE=<1/0> : Enable data acq. and calcs (can be controlled over asyn), default = disabled.
MODE=<CONT/TRIGG> : Continious or triggered mode, defaults to TRIGG
RATE=<rate in hz> : fft data sample rate in hz (must be lower than ecmc rate and (ecmc_rate/fft_rate)=integer), default = ecmc rate.
Filename = /epics/base-7.0.3.1/require/3.1.2/siteMods/ecmcPlugin_FFT/master/lib/linux-arm/libecmcPlugin_FFT.so
Config string = SOURCE=ecmc.thread.latency.max;DBG_PRINT=0;NFFT=1024;
Version = 1
Interface version = 512 (ecmc = 512)
max plc funcs = 64
max plc func args = 10
max plc consts = 64
Construct func = @0xb5022044
Enter realtime func = @0xb5022090
Exit realtime func = @0xb502203c
Realtime func = @0xb5022034
Destruct func = @0xb502206c
dlhandle = @0x1d9de28
Plc functions:
funcs[00]:
Name = "fft_clear(arg0);"
Desc = double fft_clear(index) : Clear/reset fft[index].
Arg count = 1
func = @0xb5022094
funcs[01]:
Name = "fft_enable(arg0, arg1);"
Desc = double fft_enable(index, enable) : Set enable for fft[index].
Arg count = 2
func = @0xb50220b0
funcs[02]:
Name = "fft_trigg(arg0);"
Desc = double fft_trigg(index) : Trigg new measurement for fft[index]. Will clear buffers.
Arg count = 1
func = @0xb50220d4
funcs[03]:
Name = "fft_mode(arg0, arg1);"
Desc = double fft_mode(index, mode) : Set mode Cont(1)/Trigg(2) for fft[index].
Arg count = 2
func = @0xb50220f0
funcs[04]:
Name = "fft_stat(arg0);"
Desc = double fft_stat(index) : Get status of fft (NO_STAT, IDLE, ACQ, CALC) for fft[index].
Arg count = 1
func = @0xb5022114
Plc constants:
consts[00]:
Name = "fft_CONT" = 1.000
Desc = FFT Mode: Continious
consts[01]:
Name = "fft_TRIGG" = 2.000
Desc = FFT Mode :Triggered
consts[02]:
Name = "fft_NO_STAT" = 0.000
Desc = FFT Status: Invalid state
consts[03]:
Name = "fft_IDLE" = 1.000
Desc = FFT Status: Idle state (waiting for trigger)
consts[04]:
Name = "fft_ACQ" = 2.000
Desc = FFT Status: Acquiring data
consts[05]:
Name = "fft_CALC" = 3.000
Desc = FFT Status: Calculating result
```

View File

@@ -28,6 +28,8 @@ IOC_TEST:Axis1-VelAct
IOC_TEST:Axis1-PosSet
IOC_TEST:Axis1-PosErr
IOC_TEST:Axis1-PLC-Err
IOC_TEST:Plg-Mtn0-BuffSze
IOC_TEST:Plg-Mtn0-ElmCnt
IOC_TEST:MCU-AppMode
IOC_TEST:MCU-ErrId
IOC_TEST:MCU-ThdLatMin
@@ -170,12 +172,24 @@ IOC_TEST:m0-Dom-WC-Zero
IOC_TEST:m0-Dom-WC-Incomplete
IOC_TEST:m0-Dom-WC-Complete
IOC_TEST:m0-Stat-OK
REQMOD:raspberrypi-3352:exit
REQMOD:raspberrypi-3352:MODULES
REQMOD:raspberrypi-3352:VERSIONS
REQMOD:raspberrypi-3352:MOD_VER
REQMOD:raspberrypi-1801:exit
REQMOD:raspberrypi-1801:MODULES
REQMOD:raspberrypi-1801:VERSIONS
REQMOD:raspberrypi-1801:MOD_VER
IOC_TEST:Axis1-Arr-Stat
IOC_TEST:Axis1-PLC-Expr-RB
IOC_TEST:Plg-Mtn0-PosAct-Arr
IOC_TEST:Plg-Mtn0-PosSet-Arr
IOC_TEST:Plg-Mtn0-PosErr-Arr
IOC_TEST:Plg-Mtn0-Time-Arr
IOC_TEST:Plg-Mtn0-Ena-Arr
IOC_TEST:Plg-Mtn0-EnaAct-Arr
IOC_TEST:Plg-Mtn0-Bsy-Arr
IOC_TEST:Plg-Mtn0-Exe-Arr
IOC_TEST:Plg-Mtn0-TrjSrc-Arr
IOC_TEST:Plg-Mtn0-EncSrc-Arr
IOC_TEST:Plg-Mtn0-AtTrg-Arr
IOC_TEST:Plg-Mtn0-ErrId-Arr
IOC_TEST:MCU-ErrMsg
IOC_TEST:MCU-Updated
IOC_TEST:m0s001-Enc01-LtchCmd
@@ -196,6 +210,8 @@ IOC_TEST:Axis1-DIR_
IOC_TEST:Axis1-ErrRst
IOC_TEST:Axis1-HomProc
IOC_TEST:Axis1-MtnCmdData
IOC_TEST:Plg-Mtn0-Mde-RB
IOC_TEST:Plg-Mtn0-Cmd-RB
IOC_TEST:m0s001-Stat
IOC_TEST:m0s002-Stat
IOC_TEST:Axis1-MR-ErrId
@@ -206,6 +222,7 @@ IOC_TEST:Axis1-CfgDLLM-En-RB
IOC_TEST:Axis1-Stat
IOC_TEST:Axis1-ErrId
IOC_TEST:Axis1-WrnId
IOC_TEST:Plg-Mtn0-Stat
IOC_TEST:m0-Stat
IOC_TEST:m0-SlvCntr
IOC_TEST:m0-MemmapCntr
@@ -228,17 +245,18 @@ IOC_TEST:m0-SlvRsp
IOC_TEST:m0-Dom-WC
IOC_TEST:m0s001-Enc01-LtchRst
IOC_TEST:Axis1-Cmd_
REQMOD:raspberrypi-3352:BaseVersion
REQMOD:raspberrypi-3352:require_VER
REQMOD:raspberrypi-3352:ecmccfg_VER
REQMOD:raspberrypi-3352:asyn_VER
REQMOD:raspberrypi-3352:exprtk_VER
REQMOD:raspberrypi-3352:motor_VER
REQMOD:raspberrypi-3352:ruckig_VER
REQMOD:raspberrypi-3352:ecmc_VER
REQMOD:raspberrypi-1801:BaseVersion
REQMOD:raspberrypi-1801:require_VER
REQMOD:raspberrypi-1801:ecmccfg_VER
REQMOD:raspberrypi-1801:asyn_VER
REQMOD:raspberrypi-1801:exprtk_VER
REQMOD:raspberrypi-1801:motor_VER
REQMOD:raspberrypi-1801:ruckig_VER
REQMOD:raspberrypi-1801:ecmc_VER
IOC_TEST:m0s001-HWType
IOC_TEST:m0s002-HWType
IOC_TEST:Axis1-MsgTxt
REQMOD:raspberrypi-1801:ecmc_plugin_motion_VER
IOC_TEST:m0s001-Drv01-Stat
IOC_TEST:m0s001-Enc01-Stat
IOC_TEST:m0s001-Stat_
@@ -287,6 +305,10 @@ IOC_TEST:Axis1-TgtVelCmd
IOC_TEST:Axis1-Id
IOC_TEST:MCU-Cfg-AX1-NxtObjId
IOC_TEST:MCU-Cfg-AX-FrstObjId
IOC_TEST:MCU-Cfg-PLG{Index}-NxtObjId
IOC_TEST:MCU-Cfg-PLG-FrstObjId
IOC_TEST:Plg-Mtn0-AxCmd-RB
IOC_TEST:Plg-Mtn0-SmpHz-RB
IOC_TEST:MCU-Cfg-Eng-Mode
IOC_TEST:m0s001-Enc01-LchAutRstSp
IOC_TEST:m0s002-BO01
@@ -315,6 +337,8 @@ IOC_TEST:Axis1-PLC-EnaCmd
IOC_TEST:Axis1-CmdFrmPLCCmd
IOC_TEST:Axis1-SftLimBwdEna
IOC_TEST:Axis1-SftLimFwdEna
IOC_TEST:Plg-Mtn0-EnaCmd-RB
IOC_TEST:Plg-Mtn0-TrgCmd-RB
IOC_TEST:MCU-ErrRst
IOC_TEST:Axis1-MCU1-asyn
IOC_TEST:MCU-Cmd

827
tools/ecmcMotionMainGui.py Normal file
View File

@@ -0,0 +1,827 @@
#*************************************************************************
# 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.
#
# ecmcMtnMainGui.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 os
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
# List of pv names
pvlist= [ 'BuffSze',
'ElmCnt',
'PosAct-Arr',
'PosSet-Arr',
'PosErr-Arr',
'Time-Arr',
'Ena-Arr',
'EnaAct-Arr',
'Bsy-Arr',
'Exe-Arr',
'TrjSrc-Arr',
'EncSrc-Arr',
'AtTrg-Arr',
'ErrId-Arr',
'Mde-RB',
'Cmd-RB',
'Stat',
'AxCmd-RB',
'SmpHz-RB',
'TrgCmd-RB',
'EnaCmd-RB' ]
pvmiddlestring='Plg-Mtn'
class comSignal(QObject):
data_signal = pyqtSignal(object)
class ecmcMtnMainGui(QtWidgets.QDialog):
def __init__(self,prefix=None,mtnPluginId=None):
super(ecmcMtnMainGui, self).__init__()
#Mtn plugin
self.pvnames={}
self.pvs={}
self.pv_signal_cbs={}
self.data={}
#
self.offline = False
self.pvPrefixStr = prefix
self.pvPrefixOrigStr = prefix # save for restore after open datafile
self.mtnPluginId = mtnPluginId
self.mtnPluginOrigId = mtnPluginId
self.allowSave = False
self.path = '.'
self.unitRawY = "[]"
self.unitSpectY = "[]"
self.labelSpectY = "Amplitude"
self.labelRawY = "Raw"
self.title = ""
self.NMtn = 1024
self.sampleRate = 1000
self.sampleRateValid = False
self.MtnYDataValid = False
self.MtnXDataValid = False
self.RawYDataValid = False
self.RawXDataValid = False
if prefix is None or mtnPluginId is None:
self.offline = True
self.pause = True
self.enable = False
else:
#Check for connection else go offline
self.buildPvNames()
connected = self.pvs['BuffSze'].wait_for_connection(timeout=2)
if connected:
self.offline = False
self.pause = False
else:
self.offline = True
self.pause = True
self.enable = False
# Callbacks through signals
#self.comSignalX = comSignal()
#self.comSignalX.data_signal.connect(self.callbackFuncX)
#
#self.comSignalSpectY = comSignal()
#self.comSignalSpectY.data_signal.connect(self.callbackFuncSpectY)
#self.comSignalRawData = comSignal()
#self.comSignalRawData.data_signal.connect(self.callbackFuncrawData)
#self.comSignalEnable = comSignal()
#self.comSignalEnable.data_signal.connect(self.callbackFuncEnable)
#self.comSignalMode = comSignal()
#self.comSignalMode.data_signal.connect(self.callbackFuncMode)
#self.comSignalBuffIdAct = comSignal()
#self.comSignalBuffIdAct.data_signal.connect(self.callbackFuncBuffIdAct)
self.startupDone=False
self.pause = 0
# Data
#self.dataX = None
#self.spectY = None
#self.rawdataY = None
#self.rawdataX = None
#self.enable = None
self.createWidgets()
#self.connectPvs()
self.setStatusOfWidgets()
self.resize(1000,850)
return
def createWidgets(self):
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.openBtn = QPushButton(text = 'open data')
self.openBtn.setFixedSize(100, 50)
self.openBtn.clicked.connect(self.openBtnAction)
self.saveBtn = QPushButton(text = 'save data')
self.saveBtn.setFixedSize(100, 50)
self.saveBtn.clicked.connect(self.saveBtnAction)
self.enableBtn = QPushButton(text = 'enable Mtn')
self.enableBtn.setFixedSize(100, 50)
self.enableBtn.clicked.connect(self.enableBtnAction)
self.triggBtn = QPushButton(text = 'trigg Mtn')
self.triggBtn.setFixedSize(100, 50)
self.triggBtn.clicked.connect(self.triggBtnAction)
self.zoomBtn = QPushButton(text = 'auto zoom')
self.zoomBtn.setFixedSize(100, 50)
self.zoomBtn.clicked.connect(self.zoomBtnAction)
self.modeCombo = QComboBox()
self.modeCombo.setFixedSize(100, 50)
self.modeCombo.currentIndexChanged.connect(self.newModeIndexChanged)
self.modeCombo.addItem("CONT")
self.modeCombo.addItem("TRIGG")
self.progressBar = QProgressBar()
self.progressBar.reset()
self.progressBar.setMinimum(0)
self.progressBar.setMaximum(100) #100%
self.progressBar.setValue(0)
self.progressBar.setFixedHeight(20)
# Fix layout
self.setGeometry(300, 300, 900, 700)
layoutVert = QVBoxLayout()
layoutVert.addWidget(self.toolbar)
layoutVert.addWidget(self.canvas)
layoutControl = QHBoxLayout()
layoutControl.addWidget(self.pauseBtn)
layoutControl.addWidget(self.enableBtn)
layoutControl.addWidget(self.triggBtn)
layoutControl.addWidget(self.modeCombo)
layoutControl.addWidget(self.zoomBtn)
layoutControl.addWidget(self.saveBtn)
layoutControl.addWidget(self.openBtn)
frameControl = QFrame(self)
frameControl.setFixedHeight(70)
frameControl.setLayout(layoutControl)
layoutVert.addWidget(frameControl)
layoutVert.addWidget(self.progressBar)
self.setLayout(layoutVert)
def setStatusOfWidgets(self):
self.saveBtn.setEnabled(self.allowSave)
if self.offline:
self.enableBtn.setStyleSheet("background-color: grey")
self.enableBtn.setEnabled(False)
self.pauseBtn.setStyleSheet("background-color: grey")
self.pauseBtn.setEnabled(False)
self.modeCombo.setEnabled(False)
self.triggBtn.setEnabled(False)
self.setWindowTitle("ecmc Mtn Main plot: Offline")
else:
self.modeCombo.setEnabled(True)
# Check actual value of pvs
enable = self.pvs['EnaCmd-RB'].get()
if enable is None:
print("pvs['EnaCmd-RB'].get() failed")
return
if(enable>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)
#if self.sourceStr is None:
# print("pvSource.get() failed")
# return
self.sampleRate = self.pvs['SmpHz-RB'].get()
if self.sampleRate is None:
print("pvs['SmpHz-RB'].get() failed")
return
self.sampleRateValid = True
self.mode = self.pvs['Mde-RB'].get()
if self.mode is None:
print("pvs['Mde-RB'].get() failed")
return
self.modeStr = "NO_MODE"
self.triggBtn.setEnabled(False) # Only enable if mode = TRIGG = 2
if self.mode == 1:
self.modeStr = "CONT"
self.modeCombo.setCurrentIndex(self.mode-1) # Index starta t zero
if self.mode == 2:
self.modeStr = "TRIGG"
self.triggBtn.setEnabled(True)
self.modeCombo.setCurrentIndex(self.mode-1) # Index starta t zero
self.setWindowTitle("ecmc Mtn Main plot: prefix=" + self.pvPrefixStr + " , mtnId=" + str(self.mtnPluginId) +
", rate=" + str(self.sampleRate))
def buildPvNames(self):
print('HEHEHEHHEH')
# Pv names based on structure: <prefix>Plugin-Mtn<mtnPluginId>-<suffixname>
for pv in pvlist:
self.pvnames[pv]=self.buildPvName(pv)
if self.pvnames[pv] is None:
raise RuntimeError("pvname must not be 'None'")
if len(self.pvnames[pv])==0:
raise RuntimeError("pvname must not be ''")
self.pvs[pv] = epics.PV(self.pvnames[pv])
self.pv_signal_cbs[pv] = comSignal()
# Signal callbacks (update gui)
# replace any '-' with '_' since '-' not allowed in funcion names
sig_cb_func=getattr(self,'sig_cb_' + pv.replace('-','_'))
self.pv_signal_cbs[pv].data_signal.connect(sig_cb_func)
# Pv monitor callbacks
mon_cb_func=getattr(self,'on_change_' + pv.replace('-','_'))
self.pvs[pv].add_callback(mon_cb_func)
QCoreApplication.processEvents()
def buildPvName(self, suffixname):
return self.pvPrefixStr + pvmiddlestring + str(self.mtnPluginId) + '-' + suffixname
###### Pv monitor callbacks
def on_change_BuffSze(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
print('On Change')
self.pv_signal_cbs['BuffSze'].data_signal.emit(value)
def on_change_ElmCnt(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
print('On Change')
self.pv_signal_cbs['ElmCnt'].data_signal.emit(value)
def on_change_PosAct_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
print('On Change')
self.pv_signal_cbs['PosAct-Arr'].data_signal.emit(value)
def on_change_PosSet_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
print('On Change')
self.pv_signal_cbs['PosSet-Arr'].data_signal.emit(value)
def on_change_PosErr_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
print('On Change')
self.pv_signal_cbs['PosErr-Arr'].data_signal.emit(value)
def on_change_Time_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
print('On Change')
self.pv_signal_cbs['Time-Arr'].data_signal.emit(value)
def on_change_Ena_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
print('On Change')
self.pv_signal_cbs['Ena-Arr'].data_signal.emit(value)
def on_change_EnaAct_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
print('On Change')
self.pv_signal_cbs['EnaAct-Arr'].data_signal.emit(value)
def on_change_Bsy_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
print('On Change')
self.pv_signal_cbs['Bsy-Arr'].data_signal.emit(value)
def on_change_Exe_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
print('On Change')
self.pv_signal_cbs['Exe-Arr'].data_signal.emit(value)
def on_change_TrjSrc_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
print('On Change')
self.pv_signal_cbs['TrjSrc-Arr'].data_signal.emit(value)
def on_change_EncSrc_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
print('On Change')
self.pv_signal_cbs['EncSrc-Arr'].data_signal.emit(value)
def on_change_AtTrg_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
print('On Change')
self.pv_signal_cbs['AtTrg-Arr'].data_signal.emit(value)
def on_change_ErrId_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
print('On Change')
self.pv_signal_cbs['ErrId-Arr'].data_signal.emit(value)
def on_change_Mde_RB(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
print('On Change')
self.pv_signal_cbs['Mde-RB'].data_signal.emit(value)
def on_change_Cmd_RB(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
print('On Change')
self.pv_signal_cbs['Cmd-RB'].data_signal.emit(value)
def on_change_Stat(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
print('On Change')
self.pv_signal_cbs['Stat'].data_signal.emit(value)
def on_change_AxCmd_RB(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
print('On Change')
self.pv_signal_cbs['AxCmd-RB'].data_signal.emit(value)
def on_change_SmpHz_RB(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
print('On Change')
self.pv_signal_cbs['SmpHz-RB'].data_signal.emit(value)
def on_change_TrgCmd_RB(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
print('On Change')
self.pv_signal_cbs['TrgCmd-RB'].data_signal.emit(value)
def on_change_EnaCmd_RB(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
print('On Change')
self.pv_signal_cbs['EnaCmd-RB'].data_signal.emit(value)
# def onChangePvMode(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
# if self.pause:
# return
# self.comSignalMode.data_signal.emit(value)
#
# def onChangePvEnable(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
# if self.pause:
# return
# self.comSignalEnable.data_signal.emit(value)
#
# def onChangeX(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
# if self.pause:
# return
# self.comSignalX.data_signal.emit(value)
#
# def onChangePvSpectY(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
# if self.pause:
# return
# self.comSignalSpectY.data_signal.emit(value)
#
# def onChangePvrawData(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
# if self.pause:
# return
# self.comSignalRawData.data_signal.emit(value)
#
# def onChangePvBuffIdAct(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
# if self.pause:
# return
# self.comSignalBuffIdAct.data_signal.emit(value)
###### Signal callbacks
# dbgrep *Plg*Mtn*
# IOC_TEST:Plg-Mtn0-BuffSze
# IOC_TEST:Plg-Mtn0-ElmCnt
# IOC_TEST:Plg-Mtn0-PosAct-Arr
# IOC_TEST:Plg-Mtn0-PosSet-Arr
# IOC_TEST:Plg-Mtn0-PosErr-Arr
# IOC_TEST:Plg-Mtn0-Time-Arr
# IOC_TEST:Plg-Mtn0-Ena-Arr
# IOC_TEST:Plg-Mtn0-EnaAct-Arr
# IOC_TEST:Plg-Mtn0-Bsy-Arr
# IOC_TEST:Plg-Mtn0-Exe-Arr
# IOC_TEST:Plg-Mtn0-TrjSrc-Arr
# IOC_TEST:Plg-Mtn0-EncSrc-Arr
# IOC_TEST:Plg-Mtn0-AtTrg-Arr
# IOC_TEST:Plg-Mtn0-ErrId-Arr
# IOC_TEST:Plg-Mtn0-Mde-RB
# IOC_TEST:Plg-Mtn0-Cmd-RB
# IOC_TEST:Plg-Mtn0-Stat
# IOC_TEST:Plg-Mtn0-AxCmd-RB
# IOC_TEST:Plg-Mtn0-SmpHz-RB
# IOC_TEST:Plg-Mtn0-TrgCmd-RB
# IOC_TEST:Plg-Mtn0-EnaCmd-RB
def sig_cb_BuffSze(self,value):
print('HEPP')
def sig_cb_ElmCnt(self,value):
print('HEPP')
def sig_cb_PosAct_Arr(self,value):
print('HEPP')
def sig_cb_PosSet_Arr(self,value):
print('HEPP')
def sig_cb_PosErr_Arr(self,value):
print('HEPP')
def sig_cb_Time_Arr(self,value):
if(np.size(value)) > 0:
self.dataX = value
self.MtnXDataValid = True
return
def sig_cb_Ena_Arr(self,value):
print('HEPP')
def sig_cb_EnaAct_Arr(self,value):
print('HEPP')
def sig_cb_Bsy_Arr(self,value):
print('HEPP')
def sig_cb_Exe_Arr(self,value):
print('HEPP')
def sig_cb_TrjSrc_Arr(self,value):
print('HEPP')
def sig_cb_EncSrc_Arr(self,value):
print('HEPP')
def sig_cb_AtTrg_Arr(self,value):
print('HEPP')
def sig_cb_ErrId_Arr(self,value):
print('HEPP')
def sig_cb_Mde_RB(self,value):
if value < 1 or value> 2:
self.modeStr = "NO_MODE"
print('callbackFuncMode: Error Invalid mode.')
return
self.mode = value
self.modeCombo.setCurrentIndex(self.mode-1) # Index starta t zero
if self.mode == 1:
self.modeStr = "CONT"
self.triggBtn.setEnabled(False) # Only enable if mode = TRIGG = 2
if self.mode == 2:
self.modeStr = "TRIGG"
self.triggBtn.setEnabled(True)
return
def sig_cb_Cmd_RB(self,value):
print('HEPP')
def sig_cb_Stat(self,value):
print('HEPP')
def sig_cb_AxCmd_RB(self,value):
print('HEPP')
def sig_cb_SmpHz_RB(self,value):
print('HEPP')
def sig_cb_TrgCmd_RB(self,value):
print('HEPP')
def sig_cb_EnaCmd_RB(self,value):
self.enable = value
if self.enable:
self.enableBtn.setStyleSheet("background-color: green")
else:
self.enableBtn.setStyleSheet("background-color: red")
self.data['EnaCmd-RB'] = value
return
def callbackFuncSpectY(self, value):
if(np.size(value)) > 0:
self.spectY = value
self.MtnYDataValid = self.RawXDataValid
self.plotAll()
return
def callbackFuncrawData(self, value):
if(np.size(value)) > 0:
if (self.rawdataX is None or np.size(value) != np.size(self.rawdataY)) and self.sampleRateValid:
self.rawdataX = np.arange(-np.size(value)/self.sampleRate, 0, 1/self.sampleRate)
self.RawXDataValid = True
self.rawdataY = value
self.RawYDataValid = True
self.plotAll()
return
def callbackFuncBuffIdAct(self, value):
if self.NMtn is None:
return
if(self.NMtn>0):
self.progressBar.setValue(value/self.NMtn*100)
if value/self.NMtn*100 < 80 and value/self.NMtn*100 >1:
self.MtnYDataValid = False
self.RawYDataValid = False
return
###### Widget callbacks
def pauseBtnAction(self):
self.pause = not self.pause
if self.pause:
self.pauseBtn.setStyleSheet("background-color: red")
else:
self.pvPrefixStr = self.pvPrefixOrigStr # Restore if dataset was opened
self.mtnPluginId = self.mtnPluginOrigId # Restore if dataset was opened
self.buildPvNames()
self.pauseBtn.setStyleSheet("background-color: green")
# Retrigger plots with newest values
self.comSignalSpectY.data_signal.emit(self.spectY)
self.comSignalRawData.data_signal.emit(self.rawdataY)
return
def enableBtnAction(self):
self.enable = not self.enable
self.pvs['EnaCmd-RB'].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 zoomBtnAction(self):
if self.rawdataY is None:
return
if self.rawdataX is None:
return
if self.spectY is None:
return
if self.dataX is None:
return
if self.axSpect is None:
return
# Spect
self.axSpect.autoscale(enable=True)
self.plotSpect(True)
# rawdata
self.plotRaw(True)
return
def newModeIndexChanged(self,index):
if index==0 or index==1:
if not self.offline and self.pvs['Mde-RB'] is not None:
self.pvs['Mde-RB'].put(index+1)
return
def openBtnAction(self):
#if not self.offline:
# self.pause = 1 # pause while open if online
# self.pauseBtn.setStyleSheet("background-color: red")
# QCoreApplication.processEvents()
#
#fname = QFileDialog.getOpenFileName(self, 'Open file', self.path, "Data files (*.npz)")
#if fname is None:
# return
#if np.size(fname) != 2:
# return
#if len(fname[0])<=0:
# return
#self.path = os.path.dirname(os.path.abspath(fname[0]))
#
#npzfile = np.load(fname[0])
#
## verify scope plugin
#if npzfile['plugin'] != "Mtn":
# print ("Invalid data type (wrong plugin type)")
# return
#
## File valid
#self.rawdataX = npzfile['rawdataX']
#self.rawdataY = npzfile['rawdataY']
#self.dataX = npzfile['spectX']
#self.spectY = npzfile['spectY']
#self.sampleRate = npzfile['sampleRate']
#self.NMtn = npzfile['NMtn']
#self.mode = npzfile['mode']
#self.pvPrefixStr = str(npzfile['pvPrefixStr'])
#self.mtnPluginId = npzfile['mtnPluginId']
#if 'unitRawY' in npzfile:
# self.unitRawY = str(npzfile['unitRawY'])
#if 'unitSpectY' in npzfile:
# self.unitSpectY = str(npzfile['unitSpectY'])
#if 'labelRawY' in npzfile:
# self.labelRawY = str(npzfile['labelRawY'])
#if 'labelSpectY' in npzfile:
# self.labelSpectY = str(npzfile['labelSpectY'])
#if 'title' in npzfile:
# self.title = str(npzfile['title'])
#
#self.buildPvNames()
#
## trigg draw
#self.MtnYDataValid = True
#self.MtnXDataValid = True
#self.RawYDataValid = True
#self.RawXDataValid = True
#self.sampleRateValid = True
#self.comSignalMode.data_signal.emit(self.mode)
#self.comSignalX.data_signal.emit(self.dataX)
#self.comSignalSpectY.data_signal.emit(self.spectY)
#self.comSignalRawData.data_signal.emit(self.rawdataY)
#self.setStatusOfWidgets()
#
#self.startupDone=True
#self.zoomBtnAction()
return
def saveBtnAction(self):
#fname = QFileDialog.getSaveFileName(self, 'Save file', self.path, "Data files (*.npz)")
#if fname is None:
# return
#if np.size(fname) != 2:
# return
#if len(fname[0])<=0:
# return
## Save all relevant data
#np.savez(fname[0],
# plugin = "Mtn",
# rawdataX = self.rawdataX,
# rawdataY = self.rawdataY,
# spectX = self.dataX,
# spectY = self.spectY,
# sampleRate = self.sampleRate,
# NMtn = self.NMtn,
# mode = self.mode,
# pvPrefixStr = self.pvPrefixStr,
# mtnPluginId = self.mtnPluginId,
# unitRawY = self.unitRawY,
# unitSpectY = self.unitSpectY,
# labelRawY = self.labelRawY,
# labelSpectY = self.labelSpectY,
# title = self.title
# )
#
#self.path = os.path.dirname(os.path.abspath(fname[0]))
return
def plotAll(self):
print("self.MtnYDataValid = " + str(self.MtnYDataValid))
print("self.MtnXDataValid = " + str(self.MtnXDataValid))
print("self.RawYDataValid = " + str(self.RawYDataValid))
print("self.RawXDataValid = " + str(self.RawXDataValid))
if self.MtnYDataValid and self.MtnXDataValid and self.RawYDataValid and self.RawXDataValid:
self.plotSpect()
self.plotRaw()
self.MtnYDataValid = False
self.RawYDataValid = False
###### Plotting
def plotSpect(self, autozoom=False):
if self.dataX is None:
return
if self.spectY is None:
return
# 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.dataX,self.spectY, 'b*-')
self.axSpect.grid(True)
self.axSpect.set_xlabel("Frequency [Hz]")
self.axSpect.set_ylabel(self.labelSpectY + ' ' +self.unitSpectY)
if autozoom:
ymin = np.min(self.spectY)
ymax = np.max(self.spectY)
# ensure different values
if ymin == ymax:
ymin = ymin - 1
ymax = ymax + 1
range = ymax - ymin
ymax += range * 0.1
ymin -= range * 0.1
xmin = np.min(self.dataX)
xmax = np.max(self.dataX)
if xmin == xmax:
xmin = xmin - 1
xmax = xmax + 1
range = xmax - xmin
xmax += range * 0.02
xmin -= range * 0.02
self.axSpect.set_ylim(ymin,ymax)
self.axSpect.set_xlim(xmin,xmax)
# refresh canvas
self.canvas.draw()
self.axSpect.autoscale(enable=False)
def plotRaw(self, autozoom=False):
if self.rawdataY is None:
return
if self.rawdataX 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.rawdataX,self.rawdataY, 'b*-')
self.axRaw.grid(True)
self.axRaw.set_xlabel('Time [s]')
self.axRaw.set_ylabel(self.labelRawY + ' ' + self.unitRawY)
self.axRaw.set_title(self.title)
if autozoom:
ymin = np.min(self.rawdataY)
ymax = np.max(self.rawdataY)
# ensure different values
if ymin == ymax:
ymin=ymin-1
ymax=ymax+1
range = ymax - ymin
ymax += range * 0.1
ymin -= range * 0.1
xmin = np.min(self.rawdataX)
xmax = np.max(self.rawdataX)
if xmin == xmax:
xmin = xmin - 1
xmax = xmax + 1
range = xmax - xmin
xmax += range * 0.02
xmin -= range * 0.02
self.axRaw.set_ylim(ymin,ymax)
self.axRaw.set_xlim(xmin,xmax)
# refresh canvas
self.canvas.draw()
self.allowSave = True
self.saveBtn.setEnabled(True)
self.axRaw.autoscale(enable=False)
def printOutHelp():
print("ecmcMtnMainGui: Plots waveforms of Mtn data (updates on Y data callback). ")
print("python ecmcMtnMainGui.py <prefix> <mtnId>")
print("<prefix>: Ioc prefix ('IOC_TEST:')")
print("<mtnId> : Id of mtn plugin ('0')")
print("example : python ecmcMotionMainGui.py 'IOC_TEST:' '0'")
print("Will connect to Pvs: <prefix>Plg-Mtn<mtnId>-*")
if __name__ == "__main__":
import sys
prefix = None
mtnid = None
if len(sys.argv) == 1:
prefix = None
mtnid = None
elif len(sys.argv) == 3:
prefix = sys.argv[1]
mtnid = int(sys.argv[2])
else:
printOutHelp()
sys.exit()
app = QtWidgets.QApplication(sys.argv)
window=ecmcMtnMainGui(prefix=prefix,mtnPluginId=mtnid)
window.show()
sys.exit(app.exec_())