Source code for siriushla.si_ap_fofb.graphics
"""Graphics module."""
from functools import partial as _part
import numpy as _np
from qtpy.QtCore import Qt
from qtpy.QtGui import QColor
from qtpy.QtWidgets import QToolTip, QWidget, QVBoxLayout, QLabel, \
QHBoxLayout, QCheckBox, QGridLayout
from pyqtgraph import mkBrush, mkPen
from siriuspy.namesys import SiriusPVName as _PVName
from siriuspy.devices import StrengthConv
from siriuspy.optics.constants import SI as SICte
from ..widgets import SiriusConnectionSignal as _ConnSig, QDoubleSpinBoxPlus,\
SiriusDialog
from ..as_ap_sofb.graphics.base import Graph, InfLine
from .base import BaseObject
[docs]
class MatrixWidget(BaseObject, QWidget):
"""Matrix widget."""
def __init__(self, parent, device, propty, prefix=''):
"""Init."""
BaseObject.__init__(self, device, prefix)
QWidget.__init__(self, parent)
self.setObjectName('SIApp')
self._is_inv = 'Inv' in propty
self._is_coeff = 'Coeff' in propty
self._is_hw = 'Hw' in propty
self._inflines = []
self._setupui()
self.mat = _ConnSig(self.devpref.substitute(propty=propty))
self.mat.new_value_signal[_np.ndarray].connect(self._update_graph)
self._update_graph(None)
self._update_horizontal()
def _setupui(self):
vbl = QVBoxLayout(self)
if self._is_coeff:
text = 'Corrector Coefficients'
else:
text = 'Inverse' if self._is_inv else 'Transpose'
text += ' of Response Matrix - '
text += 'Hardware Units' if self._is_hw else 'Physics Units'
lab = QLabel(text, self, alignment=Qt.AlignCenter)
lab.setStyleSheet("font-weight: bold;")
vbl.addWidget(lab)
graph = Graph(self)
vbl.addWidget(graph)
self.spbox = QDoubleSpinBoxPlus(self)
self.spbox.setMinimum(0)
self.spbox.setMaximum(1000)
value = 1.0 if self._is_coeff or self._is_inv else 80.0
self.spbox.setValue(value)
self.spbox.setKeyboardTracking(False)
self.spbox.editingFinished.connect(self._update_graph)
hbl = QHBoxLayout()
vbl.addItem(hbl)
hbl.addWidget(QLabel('Lines spacing:', self))
hbl.addWidget(self.spbox)
hbl.addStretch()
graph.setShowLegend(False)
graph.setLabel('bottom', text='BPM Position', units='m')
if self._is_coeff:
yunits = 'count/nm'
else:
if self._is_hw:
yunits = 'count/nm' if self._is_inv else 'nm/count'
else:
yunits = 'rad/m' if self._is_inv else 'm/rad'
graph.setLabel('left', text='Matrix', units=yunits)
ncorr = self._csorb.nr_chcv if self._is_coeff else self._csorb.nr_corrs
for i in range(ncorr):
color = 'blue'
if i >= self._csorb.nr_ch:
color = 'red'
if i >= self._csorb.nr_ch+self._csorb.nr_cv:
color = 'black'
opts = dict(
y_channel='',
x_channel='',
name='',
color=color,
redraw_mode=2,
lineStyle=1,
lineWidth=1,
symbol='o',
symbolSize=10)
graph.addChannel(**opts)
graph.plotItem.scene().sigMouseMoved.connect(self._show_tooltip)
self.graph = graph
def _show_tooltip(self, pos):
bname = self._csorb.bpm_nicknames
cname = self._csorb.ch_nicknames + self._csorb.cv_nicknames + ['RF', ]
bpos = self._csorb.bpm_pos
graph = self.graph
curve = graph.curveAtIndex(0)
posx = curve.scatter.mapFromScene(pos).x()
ind = _np.argmin(_np.abs(_np.array(bpos)-posx))
posy = curve.scatter.mapFromScene(pos).y()
sbval = self.spbox.value()
if sbval == 0:
indy = 0
else:
indy = int(posy // self.spbox.value())
indy = max(min(indy, len(cname)-1), 0)
txt = 'BPM = {0:s}, Corr = {1:s}'.format(bname[ind], cname[indy])
QToolTip.showText(
graph.mapToGlobal(pos.toPoint()),
txt, graph, graph.geometry(), 500)
def _update_graph(self, *args):
sep = self.spbox.value()
val = self.mat.value
if val is None:
return
if self._is_inv:
val = val.reshape(self._csorb.nr_corrs, -1)
elif self._is_coeff:
val = val.reshape(self._csorb.nr_chcv, -1)
else:
val = val.reshape(-1, self._csorb.nr_corrs)
ncorr = self._csorb.nr_chcv if self._is_coeff else self._csorb.nr_corrs
for i in range(ncorr):
cur = self.graph.curveAtIndex(i)
if self._is_inv or self._is_coeff:
cur.receiveYWaveform(sep*i + val[i, :])
else:
cur.receiveYWaveform(sep*i + val[:, i])
def _update_horizontal(self):
bpm_pos = _np.array(self._csorb.bpm_pos)
bpm_pos = [bpm_pos + i*SICte.length for i in range(2)]
bpm_pos = _np.hstack(bpm_pos)
ncorr = self._csorb.nr_chcv if self._is_coeff else self._csorb.nr_corrs
for i in range(ncorr):
cur = self.graph.curveAtIndex(i)
pos = bpm_pos
if self._is_inv or self._is_coeff:
pos = _np.hstack([-1, bpm_pos])
cur.receiveXWaveform(pos)
for cur in self._inflines:
self.graph.removeItem(cur)
self._inflines = []
for i in range(3):
dic = {'style': 2, 'width': 2, 'color': '000'}
if i == 1:
dic = {'style': 1, 'width': 3, 'color': '000'}
pen = mkPen(**dic)
line = InfLine(pos=i*SICte.length+bpm_pos[0]/2, pen=pen)
self._inflines.append(line)
self.graph.addItem(line)
[docs]
class CorrGainWidget(BaseObject, QWidget):
"""Corrector gain widget."""
def __init__(self, parent, device, prefix=''):
"""Init."""
BaseObject.__init__(self, device, prefix)
QWidget.__init__(self, parent)
self.setObjectName('SIApp')
self._setupui()
self.gains = _ConnSig(self.devpref.substitute(propty='CorrGains-Mon'))
self.gains.new_value_signal[_np.ndarray].connect(self._update_graph)
self._update_horizontal()
def _setupui(self):
vbl = QVBoxLayout(self)
lab = QLabel('Corrector Gains', self, alignment=Qt.AlignCenter)
lab.setStyleSheet("font-weight: bold;")
vbl.addWidget(lab)
for plane in ['h', 'v']:
graph = Graph(self)
name = 'graph_' + plane
setattr(self, name, graph)
graph.setTitle('Horizontal' if plane == 'h' else 'Vertical')
graph.setObjectName(name)
graph.setStyleSheet('#'+name+'{min-width: 16em; min-height: 8em;}')
graph.setLabel('bottom', text='Position', units='m')
graph.setLabel('left', text='Gain')
graph.showLegend = False
color = 'blue' if plane == 'h' else 'red'
opts = dict(
y_channel='', x_channel='', name='',
color=color, redraw_mode=2,
lineStyle=1, lineWidth=1,
symbol='o', symbolSize=10)
graph.addChannel(**opts)
graph.plotItem.scene().sigMouseMoved.connect(
_part(self._show_tooltip, plane))
vbl.addWidget(graph)
def _show_tooltip(self, plane, pos):
if plane == 'h':
cname = self._csorb.ch_nicknames
xdata = self._csorb.ch_pos
else:
cname = self._csorb.cv_nicknames
xdata = self._csorb.cv_pos
graph = getattr(self, 'graph_' + plane)
curve = graph.curveAtIndex(0)
posx = curve.scatter.mapFromScene(pos).x()
ind = _np.argmin(_np.abs(_np.array(xdata)-posx))
txt = '{0:s}'.format(cname[ind])
QToolTip.showText(
graph.mapToGlobal(pos.toPoint()),
txt, graph, graph.geometry(), 500)
def _update_graph(self, *args):
val = self.gains.value
if val is None:
return
curve = self.graph_h.curveAtIndex(0)
curve.receiveYWaveform(_np.array(val[:self._csorb.nr_ch]))
curve = self.graph_v.curveAtIndex(0)
curve.receiveYWaveform(_np.array(val[self._csorb.nr_ch:]))
def _update_horizontal(self):
curve = self.graph_h.curveAtIndex(0)
curve.receiveXWaveform(_np.array(self._csorb.ch_pos))
curve = self.graph_v.curveAtIndex(0)
curve.receiveXWaveform(_np.array(self._csorb.cv_pos))
[docs]
class RefOrbViewWidget(BaseObject, SiriusDialog):
"""RefOrb View widget."""
def __init__(self, parent, device, prefix=''):
"""Init."""
BaseObject.__init__(self, device, prefix)
SiriusDialog.__init__(self, parent)
self.setObjectName('SIApp')
self._setupui()
self.refx = _ConnSig(self.devpref.substitute(propty='RefOrbX-RB'))
self.refx.new_value_signal[_np.ndarray].connect(
_part(self._update_implemented_ref, 'x'))
self.refy = _ConnSig(self.devpref.substitute(propty='RefOrbY-RB'))
self.refy.new_value_signal[_np.ndarray].connect(
_part(self._update_implemented_ref, 'y'))
self._update_horizontal()
def _setupui(self):
vbl = QVBoxLayout(self)
lab = QLabel('Reference Orbit', self, alignment=Qt.AlignCenter)
lab.setStyleSheet("font-weight: bold;")
vbl.addWidget(lab)
for plane in ['x', 'y']:
graph = Graph(self)
name = 'graph_' + plane
setattr(self, name, graph)
graph.setTitle('Horizontal' if plane == 'x' else 'Vertical')
graph.setObjectName(name)
graph.setStyleSheet(
'#'+name+'{min-width: 45em; min-height: 15em;}')
graph.setLabel('bottom', text='BPM Position', units='m')
graph.setLabel('left', text=plane, units='m')
color = 'blue' if plane == 'x' else 'red'
opts = dict(
y_channel='', x_channel='', name='Implemented',
color=color, redraw_mode=2,
lineStyle=1, lineWidth=2,
symbol='o', symbolSize=10)
graph.addChannel(**opts)
graph.plotItem.scene().sigMouseMoved.connect(
_part(self._show_tooltip, plane))
graph.setShowLegend(False)
vbl.addWidget(graph)
def _show_tooltip(self, plane, pos):
graph = getattr(self, 'graph_' + plane)
curve = graph.curveAtIndex(0)
posx = curve.scatter.mapFromScene(pos).x()
ind = _np.argmin(_np.abs(_np.array(self._csorb.bpm_pos)-posx))
txt = '{0:s}'.format(self._csorb.bpm_nicknames[ind])
QToolTip.showText(
graph.mapToGlobal(pos.toPoint()),
txt, graph, graph.geometry(), 500)
def _update_horizontal(self):
bpm_pos = _np.array(self._csorb.bpm_pos)
for graph in [self.graph_x, self.graph_y]:
curve = graph.curveAtIndex(0)
curve.receiveXWaveform(bpm_pos)
def _update_implemented_ref(self, plane, *args):
ref = getattr(self, 'ref' + plane)
value = ref.value
if value is None:
return
graph = getattr(self, 'graph_' + plane)
curve = graph.curveAtIndex(0)
curve.receiveYWaveform(self.UM2M*_np.array(value))
[docs]
class KickWidget(BaseObject, QWidget):
"""Corrector kicks widget."""
def __init__(self, parent, device, prefix=''):
"""Init."""
BaseObject.__init__(self, device, prefix)
QWidget.__init__(self, parent)
self.setObjectName('SIApp')
self._setupui()
self._psconv = {
psn: StrengthConv(psn, 'Ref-Mon', auto_monitor_mon=True)
for psn in self._csorb.ch_names + self._csorb.cv_names}
for suffix in ['-Mon', 'Acc-Mon', 'Ref-Mon']:
for plane in ['h', 'v']:
propty = 'KickC' + plane.upper() + suffix
ktype = 'kick' + suffix.strip('-Mon').lower()
pvn = self.devpref.substitute(propty=propty)
chn = _ConnSig(pvn)
chn.new_value_signal[_np.ndarray].connect(
_part(self._update_graph, ktype, plane))
setattr(self, ktype+plane, chn)
self.limh = _ConnSig(self.devpref.substitute(propty='CHAccSatMax-RB'))
self.limh.new_value_signal[float].connect(
_part(self._update_graph, 'lim', 'h'))
self.limv = _ConnSig(self.devpref.substitute(propty='CVAccSatMax-RB'))
self.limv.new_value_signal[float].connect(
_part(self._update_graph, 'lim', 'v'))
self.enblh = _ConnSig(self.devpref.substitute(propty='CHEnblList-RB'))
self.enblh.new_value_signal[_np.ndarray].connect(
_part(self._update_graph, 'enbl', 'h'))
self.enblv = _ConnSig(self.devpref.substitute(propty='CVEnblList-RB'))
self.enblv.new_value_signal[_np.ndarray].connect(
_part(self._update_graph, 'enbl', 'v'))
self.energy = _ConnSig(_PVName('SI-Fam:PS-B1B2-1').substitute(
prefix=self.prefix, propty='EnergyRef-Mon'))
self.energy.new_value_signal[float].connect(
_part(self._update_graph, 'energy', 'h'))
self.energy.new_value_signal[float].connect(
_part(self._update_graph, 'energy', 'v'))
self._update_horizontal()
def _setupui(self):
lay = QGridLayout(self)
lab = QLabel(
'<h4>Fast Corrector Kicks</h4>', self, alignment=Qt.AlignCenter)
lay.addWidget(lab, 0, 0, 1, 2)
cbkicks = dict()
hview = QHBoxLayout()
hview.addWidget(QLabel('Show: ', self))
for curve in ['Acc', 'Ref', 'Mon']:
cbx = QCheckBox(curve, self)
checked = curve == 'Acc'
cbx.setChecked(checked)
cbkicks[curve] = cbx
hview.addWidget(cbx)
lay.addLayout(hview, 1, 0, alignment=Qt.AlignLeft)
cblim = QCheckBox('Show Kick Limits', self)
cblim.setChecked(True)
lay.addWidget(cblim, 1, 1, alignment=Qt.AlignRight)
row = 2
for plane in ['h', 'v']:
graph = Graph(self)
name = 'graph_' + plane
setattr(self, name, graph)
graph.setTitle('Horizontal' if plane == 'h' else 'Vertical')
graph.setObjectName(name)
graph.setStyleSheet(
'#'+name+'{min-width: 45em; min-height: 15em;}')
graph.setLabel('bottom', text='Position', units='m')
graph.setLabel('left', text='Kick', units='rad')
graph.maxRedrawRate = 8 # [Hz]
# kicks
opts = dict(
y_channel='', x_channel='', redraw_mode=2,
lineStyle=1, lineWidth=1, symbol='o', symbolSize=10)
# Current-Mon
opts['color'] = 'gray'
opts['name'] = 'Mon'
graph.addChannel(**opts)
curve = graph.curveAtIndex(0)
curve.setVisible(False)
cbkicks[opts['name']].toggled.connect(curve.setVisible)
# CurrentRef-Mon
opts['color'] = QColor(0, 125, 255) if plane == 'h' \
else QColor(255, 125, 0)
opts['name'] = 'Ref'
graph.addChannel(**opts)
curve = graph.curveAtIndex(1)
curve.setVisible(False)
cbkicks[opts['name']].toggled.connect(curve.setVisible)
# FOFBAcc-Mon
opts['color'] = 'blue' if plane == 'h' else 'red'
opts['name'] = 'Acc'
graph.addChannel(**opts)
curve = graph.curveAtIndex(2)
cbkicks[opts['name']].toggled.connect(curve.setVisible)
# limits
opts = dict(
y_channel='', x_channel='',
color='black', redraw_mode=2,
lineStyle=2, lineWidth=1)
opts['name'] = 'maxlim'
graph.addChannel(**opts)
graph.legend.removeItem('maxlim')
maxkick = graph.curveAtIndex(3)
opts['name'] = 'minlim'
graph.addChannel(**opts)
minkick = graph.curveAtIndex(4)
graph.legend.removeItem('minlim')
cblim.toggled.connect(maxkick.setVisible)
cblim.toggled.connect(minkick.setVisible)
graph.plotItem.scene().sigMouseMoved.connect(
_part(self._show_tooltip, plane))
lay.addWidget(graph, row, 0, 1, 2)
row += 1
def _show_tooltip(self, plane, pos):
if plane == 'h':
cname = self._csorb.ch_nicknames
xdata = self._csorb.ch_pos
else:
cname = self._csorb.cv_nicknames
xdata = self._csorb.cv_pos
graph = getattr(self, 'graph_' + plane)
curve = graph.curveAtIndex(0)
posx = curve.scatter.mapFromScene(pos).x()
ind = _np.argmin(_np.abs(_np.array(xdata)-posx))
txt = '{0:s}'.format(cname[ind])
QToolTip.showText(
graph.mapToGlobal(pos.toPoint()),
txt, graph, graph.geometry(), 500)
def _update_graph(self, data, plane, value):
if value is None:
return
graph = getattr(self, 'graph_' + plane)
if data.startswith('kick'):
value = _np.asarray(value)*self.URAD2RAD
idx = 2 if data.endswith('acc') else \
1 if data.endswith('ref') else 0
curve = graph.curveAtIndex(idx)
curve.receiveYWaveform(value)
elif data == 'enbl':
value = _np.asarray(value)
offcor = QColor('black')
offbrs, offpen, offsz = mkBrush(offcor), mkPen(offcor), 10
for cidx in range(3):
curve = graph.curveAtIndex(cidx)
if curve.latest_y is None:
continue
onrgb = (127, 127, 127) if cidx == 0 else \
(0, 125*(cidx == 1), 255) if plane == 'h' \
else (255, 125*(cidx == 1), 0)
oncor = QColor(*onrgb)
onbrs, onpen, onsize = mkBrush(oncor), mkPen(oncor), 10
brss, pens, sizes = [], [], []
for val in value:
if val:
brss.append(onbrs)
pens.append(onpen)
sizes.append(onsize)
else:
brss.append(offbrs)
pens.append(offpen)
sizes.append(offsz)
curve.opts['symbolBrush'] = brss
curve.opts['symbolPen'] = pens
curve.opts['symbolSize'] = sizes
curve.redrawCurve()
else:
if data == 'energy':
value = self.limh.value if plane == 'h' \
else self.limv.value
if value is None:
return
psnames = self._csorb.ch_names if plane == 'h' \
else self._csorb.cv_names
maxlim = [
self._psconv[psn].conv_current_2_strength(value)
for psn in psnames]
if None in maxlim:
return
maxlim = _np.array(maxlim)*self.URAD2RAD
maxc = graph.curveAtIndex(3)
maxc.receiveYWaveform(maxlim)
minlim = [
self._psconv[psn].conv_current_2_strength(-value)
for psn in psnames]
if None in minlim:
return
minlim = _np.array(minlim)*self.URAD2RAD
minc = graph.curveAtIndex(4)
minc.receiveYWaveform(minlim)
def _update_horizontal(self):
for plane in ['h', 'v']:
graph = getattr(self, 'graph_' + plane)
data = self._csorb.ch_pos if plane == 'h' else self._csorb.cv_pos
data = _np.array(data)
for idx in range(5):
curve = graph.curveAtIndex(idx)
curve.receiveXWaveform(data)