123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885 |
- """
- This Module implements the Plot Windows used in KCG
- """
- import pyqtgraph as pg
- try: # on some versions of pyqtgraph useOpenGl is no valid option
- pg.setConfigOption('useOpenGl', True)
- except:
- pass
- pg.setConfigOption('background', 'w')
- pg.setConfigOption('foreground', 'k')
- import numpy as np
- from PyQt4 import QtCore, QtGui
- from groupedelements import live_plot_windows
- from backend import io
- from backend import board
- from backend.board import available_boards
- import kcgwidget as kcgw
- tr = kcgw.tr
- LIVE = 1
- FILE = 2
- ERROR = 99
- NO_DATA = 98
- class Enum():
- """
- Simple Enum Class (as this is not supported by clean python)
- """
- def __init__(self, *args):
- self.idx = 0
- for i in args:
- setattr(self, i, self.idx)
- self.idx += 1
- plotList = [tr("Label", "Heatmap"), tr("Label", "FFT"), tr("Label", "Trains"), tr("Label", "Combined"), tr("Label", "Compare")]
- PlotType = Enum(*[str(i) for i in plotList])
- # gradient = {'mode': 'rgb',
- # 'ticks': [(0.0, (0.37281, 0.11883, 3.53583, 255)),
- # (0.0390625, (4.9401150000000005, 3.858915, 22.635585, 255)),
- # (0.078125, (15.6417, 9.330449999999999, 45.29871, 255)),
- # (0.1171875, (29.74728, 12.13137, 69.44185499999999, 255)),
- # (0.15625, (46.774395000000005, 10.283895, 90.51760499999999, 255)),
- # (0.1953125, (64.1631, 9.614775, 102.86139, 255)),
- # (0.234375, (80.65191, 13.63995, 108.40458, 255)),
- # (0.2734375, (96.64525499999999, 19.444515, 110.343345, 255)),
- # (0.3125, (112.507785, 25.33119, 110.05646999999999, 255)),
- # (0.3515625, (128.390715, 31.001625, 107.95578, 255)),
- # (0.390625, (144.29277, 36.609585, 104.10579, 255)),
- # (0.4296875, (160.100985, 42.476625, 98.50038, 255)),
- # (0.46875, (175.606515, 49.020945, 91.188765, 255)),
- # (0.5078125, (190.517385, 56.70639, 82.32827999999999, 255)),
- # (0.546875, (204.477105, 65.96187, 72.190245, 255)),
- # (0.5859375, (217.10292, 77.07629999999999, 61.10718, 255)),
- # (0.625, (228.047775, 90.11674500000001, 49.36392, 255)),
- # (0.6640625, (237.05922, 104.927145, 37.068585, 255)),
- # (0.703125, (243.99726, 121.21578, 24.147225, 255)),
- # (0.7421875, (248.797635, 138.66849, 11.122589999999999, 255)),
- # (0.78125, (251.41776000000002, 157.01625, 6.52596, 255)),
- # (0.8203125, (251.80332, 176.04333, 20.397450000000003, 255)),
- # (0.859375, (249.90816, 195.543435, 42.420015, 255)),
- # (0.8984375, (245.92047, 215.18124, 69.714705, 255)),
- # (0.9375, (241.63647, 233.936745, 104.719575, 255)),
- # (0.9765625, (245.26206, 248.86062, 145.840875, 255)),
- # (0.99609375, (252.03231, 254.58282, 164.45562, 255))]}
- gradient = {'mode': 'rgb',
- 'ticks': [(0.0, (0.37281, 0.11883, 3.53583, 255)),
- (0.1171875, (29.74728, 12.13137, 69.44185499999999, 255)),
- (0.234375, (80.65191, 13.63995, 108.40458, 255)),
- (0.3515625, (128.390715, 31.001625, 107.95578, 255)),
- (0.46875, (175.606515, 49.020945, 91.188765, 255)),
- (0.5859375, (217.10292, 77.07629999999999, 61.10718, 255)),
- (0.703125, (243.99726, 121.21578, 24.147225, 255)),
- (0.8203125, (251.80332, 176.04333, 20.397450000000003, 255)),
- (0.9375, (241.63647, 233.936745, 104.719575, 255)),
- (0.99609375, (252.03231, 254.58282, 164.45562, 255))]}
- class CustomGradientEditorItem(pg.GradientEditorItem):
- """
- A Gradient Editor Item to insert a perception linear gradient
- """
- def __init__(self, **kwargs):
- pg.GradientEditorItem.__init__(self, **kwargs)
- self.customGradients = {}
- def addGrad(self, name, grad_dic):
- """
- Add a gradient to the list of gradients in the gui
- :param name: the name of the gradient
- :param grad_dic: the dictionary containing the gradient data
- """
- if name not in self.customGradients:
- self.customGradients[name] = grad_dic
- px = QtGui.QPixmap(100, 15)
- p = QtGui.QPainter(px)
- self.restoreState(grad_dic)
- length_backup = self.length
- self.length = 100
- grad = self.getGradient()
- self.length = length_backup
- brush = QtGui.QBrush(grad)
- p.fillRect(QtCore.QRect(0, 0, 100, 15), brush)
- p.end()
- label = QtGui.QLabel()
- label.setPixmap(px)
- label.setContentsMargins(1, 1, 1, 1)
- act = QtGui.QWidgetAction(self)
- act.setDefaultWidget(label)
- act.triggered.connect(self.contextMenuClicked)
- act.name = name
- if len(self.customGradients) > 1:
- self.menu.insertAction(self.menu.actions()[0], act)
- else:
- sep = self.menu.insertSeparator(self.menu.actions()[0])
- self.menu.insertAction(sep, act)
- self.restoreState(grad_dic)
- def restoreState(self, state):
- """
- Reimplemented of pyqtgraph.GradientEditorItem.restoreState to work with our custom perception linear gradient.
- :param state: the state to restore to
- """
- self.setColorMode(state['mode'])
- for t in list(self.ticks.keys()):
- self.removeTick(t, finish=False)
- self.bottomTick = [None, 10]
- self.topmostTick = [None, -1]
- for t in state['ticks']:
- c = QtGui.QColor(*t[1])
- tick = self.addTick(t[0], c, finish=False)
- if t[0] < self.bottomTick[1]:
- self.bottomTick = [tick, t[0]]
- if t[0] > self.topmostTick[1]:
- self.topmostTick = [tick, t[0]]
- self.tickPosInPercent = self.ticks.copy()
- for t in self.ticks:
- if t is self.topmostTick[0] or t is self.bottomTick[0]:
- continue
- t.hide()
- t.movable = False
- # self.bottomTick[1] *= self.length
- # self.topmostTick[1] *= self.length
- self.updateGradient()
- self.sigGradientChangeFinished.emit(self)
- def loadPreset(self, name):
- """
- Reimplemented of pyqtgraph.GradientEditorItem.loadPreset to work with our custom perception linear gradient.
- :param name: the name of the preset to load
- """
- if name in self.customGradients:
- self.restoreState(self.customGradients[name])
- else:
- super(CustomGradientEditorItem, self).loadPreset(name)
- def tickMoved(self, tick, pos):
- """
- Reimplemented of pyqtgraph.GradientEditorItem.tickMoved to work with our custom perception linear gradient,
- which has a lot of steps which would create a lot of ticks in the gradient legend. This removes all but 2
- ticks and aligns all the internal ticks accordingly.
- :param tick: the tick to move
- :param pos: the position to move to
- """
- if tick is self.bottomTick[0]:
- # self.bottomTick[1] = pos.x()
- pg.TickSliderItem.tickMoved(self, tick, pos)
- elif tick is self.topmostTick[0]:
- # self.topmostTick[1] = pos.x()
- pg.TickSliderItem.tickMoved(self, tick, pos)
- if tick in [self.bottomTick[0], self.topmostTick[0]]: # if bottom or topmost tick was moved
- if pos.x() < self.length and pos.x() > 0: # if tick is not at top or bottom
- newUnit = (self.topmostTick[0].pos().x() - self.bottomTick[0].pos().x()) # create new virtual unit length
- for t in self.ticks: # for every tick
- if t not in [self.bottomTick[0], self.topmostTick[0]]: # if tick is not bottom or topmost tick
- pos = t.pos()
- new_x = self.bottomTick[0].pos().x() + self.tickPosInPercent[t] * newUnit
- pos.setX(new_x)
- t.setPos(pos)
- pg.TickSliderItem.tickMoved(self, t, pos)
- self.updateGradient()
- class SpectrogramColorLegendItem(pg.GraphicsWidget):
- """
- The Item used as Legend for Heatmap and FFT Plot
- """
- gradientChanged = QtCore.pyqtSignal()
- def __init__(self, img=None):
- """
- Initialise the Legend
- :param img: (ImageItem) the image item this is the legend for
- :return: -
- """
- super(SpectrogramColorLegendItem, self).__init__()
- self.layout = QtGui.QGraphicsGridLayout()
- self.setLayout(self.layout)
- self.img = img
- # self.gei = pg.GradientEditorItem(orientation='right')
- self.gei = CustomGradientEditorItem(orientation='right', allowAdd=False)
- self.legend_axis = pg.AxisItem(orientation='left')
- # self.addItem(self.legend_axis)
- self.layout.addItem(self.legend_axis, 0, 0, alignment=QtCore.Qt.AlignVCenter)
- # self.addItem(self.gei)
- self.layout.addItem(self.gei, 0, 1)
- self.gei.sigGradientChanged.connect(self.gradient_changed)
- # self.gei.loadPreset('spectrum')
- self.gei.addGrad('inferno', gradient)
- # self.setFixedWidth(80)
- self.image_changed()
- def gradient_changed(self):
- """
- Proxy function for the gradient_cnahged event of the GradientEditorItem
- :return:
- """
- self.gradientChanged.emit()
- def set_image(self, img):
- """
- Set the Image this is the legend for (only needed if not already done upon initialisation)
- :param img: (ImageItem) The image item to use
- :return: -
- """
- self.img = img
- self.image_changed(True)
- def update_axis(self):
- """
- Update the axis of this legend
- :return: -
- """
- if self.img.getLevels():
- # min, max = self.img.getLevels()
- min, max = (np.min(self.img.image), np.max(self.img.image))
- self.legend_axis.setRange(min, max)
- def reset_gradient(self):
- """
- Reset the gradient to the preset "spectrum"
- :return: -
- """
- # self.gei.loadPreset('spectrum')
- self.gei.loadPreset('inferno')
- # self.gei.restoreState(gradient)
- def image_changed(self, reset_gradient=True):
- """
- Call this to adjust the legend when the image has changed
- :param reset_gradient: (bool) whether to reset the gradient or not
- :return:
- """
- if self.img:
- self.update_axis()
- if reset_gradient:
- self.reset_gradient()
- def getLookupTable(self, nPts, alpha=None):
- """
- Get the look up table of the imageitem
- :param nPts: (int) number of points the lookup table is defined on
- :param alpha: ??
- :return: -
- """
- return self.gei.getLookupTable(nPts, alpha=alpha)
- def resizeEvent(self, event):
- """
- Handle resizing of the window
- """
- self.legend_axis.setHeight(self.gei.length)
- class SubPlotWidget(pg.GraphicsLayoutWidget):
- """
- The Widget actually containing the plots and images
- """
- def __init__(self, dType=FILE):
- super(SubPlotWidget, self).__init__()
- self.dType = dType
- self._type_changed = False
- # self.setStyleSheet("border: 5px solid black; margin: 10px;")
- self.plotType = 1
- self.plotItem = pg.PlotItem()
- self.img = pg.ImageItem()
- self.plotItem2 = pg.PlotItem()
- self.img2 = pg.ImageItem()
- self.plotItem2.addItem(self.img2)
- self.setFrameStyle(self.StyledPanel | self.Sunken)
- self.gradient_legend = SpectrogramColorLegendItem(self.img)
- def changelut():
- """
- Handle changing of the lookup table
- """
- self.img.setLookupTable(self.gradient_legend.getLookupTable(512))
- self.img2.setLookupTable(self.gradient_legend.getLookupTable(512))
- self.gradient_legend.gradientChanged.connect(changelut)
- self.addItem(self.plotItem)
- self.plotItem.addItem(self.img)
- self.plotItemPlotScatter = pg.PlotDataItem(pen=None, symbolPen=pg.mkPen(50, 50, 255), symbolSize=3, pxMode=True, symbolBrush=pg.mkBrush(50, 50, 255), downsample=200)
- self.plotItemPlotScatter1 = pg.PlotDataItem(pen=None, symbolPen=pg.mkPen(255, 50, 50), symbolSize=3, pxMode=True, symbolBrush=pg.mkBrush(255, 50, 50), downsample=200)
- # self.plotItemPlotScatter2 = pg.PlotDataItem(pen=None, symbolPen=pg.mkPen(50, 255, 50), symbolSize=3, pxMode=True, symbolBrush=pg.mkBrush(50, 255, 50), downsample=200)
- self.plotItemPlot = pg.PlotDataItem() # pen="#000000")
- self.plotItem.addItem(self.plotItemPlot)
- self.plotItem.addItem(self.plotItemPlotScatter)
- self.plotItem.addItem(self.plotItemPlotScatter1)
- # self.plotItem.addItem(self.plotItemPlotScatter2)
- self.plotItem.vb.origAutoRange = self.plotItem.vb.autoRange
- self.error_label = pg.LabelItem("Error during plot.", color=(200, 50, 50))
- self.error_label.hide()
- self.error_label.scale(1, -0.6)
- self.error_label.translate(0, -30)
- self.plotItem.addItem(self.error_label)
- self.no_data_label = pg.LabelItem("No data to plot.", color=(200, 50, 50))
- self.no_data_label.hide()
- self.no_data_label.scale(1, -0.6)
- self.no_data_label.translate(0, -30)
- self.plotItem.addItem(self.no_data_label)
- def _enableCustomAutoRange(self, data):
- """
- Enable custom auto range in this plot
- :param data: the data to autorange
- :return:
- """
- def newAutoRange(*args, **kwargs):
- ''' function to handle the new autorange '''
- bounds = [np.min(data), np.max(data)]
- self.plotItem.vb.setRange(xRange=[0, len(data)],
- yRange=[bounds[0]-0.1*(bounds[1]-bounds[0])-1, bounds[1]+0.1*(bounds[1]-bounds[0])+1], update=True)
- self.plotItem.update()
- self.plotItem.vb.autoRange = newAutoRange
- self.plotItem.autoBtn.clicked.disconnect()
- self.plotItem.autoBtn.clicked.connect(newAutoRange)
- def _disableCustomAutoRange(self):
- """
- Disable the custom autorange and reset it to default
- :return: -
- """
- self.plotItem.vb.autoRange = self.plotItem.vb.origAutoRange
- self.plotItem.autoBtn.clicked.disconnect()
- self.plotItem.autoBtn.clicked.connect(self.plotItem.autoBtnClicked)
- @QtCore.pyqtSlot(np.ndarray, tuple, tuple)
- def plot(self, data, xvalueborders=None, yvalueborders=None, autorange=True):
- """
- Plot Data. The plot type depends on the type property
- :param data: (dataset.DataSet) data to plot
- :param xvalueborders: (touple) the borders for the xvalues
- :param yvalueborders: (touple) the borders for the yvalues
- :param autorange: (bool) whether to perform a autorange or not
- :return: -
- """
- self.error_label.hide()
- self.no_data_label.hide()
- self.plotItemPlot.clear()
- self.plotItemPlotScatter.clear()
- self.plotItemPlotScatter1.clear()
- # self.plotItemPlotScatter2.clear()
- self.plotItem.resetTransform()
- self.plotItem.getAxis('bottom').setScale()
- self.img.show()
- if self._type_changed:
- self.plotItem.autoBtnClicked()
- try:
- self.removeItem(self.plotItem2)
- except Exception as e:
- if "Could not determine index of item" in str(e):
- pass
- else:
- raise
- if self.plotType == PlotType.FFT or self.plotType == PlotType.Heatmap:
- self.addItem(self.gradient_legend)
- self.gradient_legend.show()
- self.img.setImage(data, scale=[1, 1/1000.])
- self.img.resetTransform()
- self.img.setAutoDownsample(True)
- if autorange:
- self.gradient_legend.image_changed()
- if xvalueborders:
- self.plotItem.getAxis('bottom').setScale((xvalueborders[1]-xvalueborders[0])/float(data.shape[0]))
- self.img.translate(xvalueborders[0]/self.plotItem.getAxis('bottom').scale, 0)
- else:
- self.plotItem.getAxis('bottom').setScale()
- if yvalueborders:
- self.img.translate(0, yvalueborders[0])
- self.plotItem.getAxis('left').setScale(yvalueborders[1]/float(data.shape[1]))
- self._disableCustomAutoRange()
- self.plotItem.setClipToView(True)
- if self.plotType == PlotType.Compare:
- self.addItem(self.plotItem2)
- self.plotItem2.vb.linkView(self.plotItem2.vb.XAxis, self.plotItem.vb)
- self.plotItem2.vb.linkView(self.plotItem2.vb.YAxis, self.plotItem.vb)
- self.addItem(self.gradient_legend)
- self.gradient_legend.show()
- self.img.setImage(data[0], scale=[1, 1/1000.])
- self.img.show()
- self.img.resetTransform()
- self.img2.setImage(data[1], scale=[1, 1/1000.])
- self.img2.show()
- self.img2.resetTransform()
- self.img.setAutoDownsample(True)
- self.img2.setAutoDownsample(True)
- if autorange:
- self.gradient_legend.image_changed()
- pass
- if xvalueborders:
- self.plotItem.getAxis('bottom').setScale((xvalueborders[1]-xvalueborders[0])/float(data[0].shape[0]))
- self.plotItem2.getAxis('bottom').setScale((xvalueborders[1]-xvalueborders[0])/float(data[1].shape[0]))
- self.img.translate(xvalueborders[0]/self.plotItem.getAxis('bottom').scale, 0)
- self.img2.translate(xvalueborders[0]/self.plotItem.getAxis('bottom').scale, 0)
- else:
- self.plotItem.getAxis('bottom').setScale()
- self.plotItem2.getAxis('bottom').setScale()
- if yvalueborders:
- self.img.translate(0, yvalueborders[0])
- self.img2.translate(0, yvalueborders[0])
- self.plotItem.getAxis('left').setScale(yvalueborders[1]/float(data[0].shape[1]))
- self.plotItem2.getAxis('left').setScale(yvalueborders[1]/float(data[1].shape[1]))
- self._disableCustomAutoRange()
- self.plotItem.setClipToView(True)
- if self.plotType == PlotType.Trains:
- self.img.clear()
- self.plotItemPlot.show()
- self.gradient_legend.hide()
- try:
- self.removeItem(self.gradient_legend)
- pass
- except Exception, e:
- if not "Could not determine index of item" in str(e):
- raise
- self.plotItemPlotScatter.clear()
- self.plotItemPlotScatter1.clear()
- self.plotItemPlot.setData(data)
- self._enableCustomAutoRange(data)
- if self.plotType == PlotType.Combined:
- self.img.clear()
- self.plotItemPlotScatter.show()
- self.plotItemPlotScatter1.show()
- self.gradient_legend.hide()
- try:
- self.removeItem(self.gradient_legend)
- pass
- except Exception, e:
- if not "Could not determine index of item" in str(e):
- raise
- self.plotItemPlot.clear()
- self.plotItemPlotScatter.setData(data[0].transpose())
- self.plotItemPlotScatter1.setData(data[1].transpose())
- self._enableCustomAutoRange(data[0][1])
- self.plotItem.setClipToView(False) # NOTE: otherwise only a very small portion of data is visible :wonder:
- if self.plotType == ERROR or self.plotType == NO_DATA:
- self.img.hide()
- self.gradient_legend.hide()
- self.img2.hide()
- self.plotItemPlot.hide()
- self.plotItemPlotScatter.hide()
- self.plotItemPlotScatter1.hide()
- if self.plotType == ERROR:
- self.error_label.show()
- else:
- self.no_data_label.show()
- if autorange:
- self.plotItem.getViewBox().autoRange()
- self.plotItem.getViewBox().autoRange()
- self.labelAxes()
- if self.plotType in [PlotType.FFT, PlotType.Heatmap, PlotType.Compare]:
- self.gradient_legend.update_axis()
- def labelAxes(self):
- """
- Add Labels to the axis depending on the self.plotType property.
- :return: -
- """
- if self.plotType == PlotType.Heatmap:
- self.plotItem.setLabel('bottom', 'Turn', '')
- self.plotItem.setLabel('left', 'Bunch Position', '')
- elif self.plotType == PlotType.FFT:
- self.plotItem.setLabel('left', 'Bunch Position', '')
- self.plotItem.setLabel('bottom', 'Frequency', 'Hz')
- elif self.plotType == PlotType.Trains:
- self.plotItem.setLabel('left', '', '')
- self.plotItem.setLabel('bottom', 'Sample Point', '')
- elif self.plotType == PlotType.Combined:
- self.plotItem.setLabel('left', '', '')
- self.plotItem.setLabel('bottom', '', '')
- if self.plotType == PlotType.Compare:
- self.plotItem.setLabel('bottom', 'Turn', '')
- self.plotItem.setLabel('left', 'Bunch Position', '')
- self.plotItem2.setLabel('bottom', 'Turn', '')
- self.plotItem2.setLabel('left', 'Bunch Position', '')
- def changeType(self, type):
- """
- Change the plot Type
- :param type: (int) the new type
- :return: -
- """
- if type != self.plotType:
- self._type_changed = True
- self.plotType = type
- # if type in [PlotType.FFT, PlotType.Compare, PlotType.Heatmap]: # this will generate an error
- # self.gradient_legend.image_changed()
- class PlotWidget(kcgw.KCGWidgets):
- """
- The container Class holding various buttons and controls and the actual plots as SubPlotWidgets instance
- """
- close_signal = QtCore.pyqtSignal()
- change_type_signal = QtCore.pyqtSignal(int, int, str)
- def __init__(self, board_id, parent=None, name=None, unique_id=None, type=None, datatype=None, prefix=None, fName=None, data=None):
- """
- Initialise the Plot Widgt
- :param parent: (QWidget) the parent widget
- :param name: (str) name of this widget
- :param unique_id: (int) unique id of this widget
- :param type: (int) type of this widget
- :param datatype: (int) datatype (LIVE or FILE)
- :param prefix: (str) prefix for text in the listview in LeftBar
- :param fName: (str) the filename (this is only used if datatype is FILE)
- :param data: (dataset.DataSet) the data to be plotted
- :return: -
- """
- super(PlotWidget, self).__init__()
- self.board_id = board_id
- if name != None:
- self.theName = name
- if unique_id != None:
- self.theId = unique_id
- if type != None:
- self.theType = type
- self._old_type = type
- if datatype != None:
- self.theDataType = datatype
- if prefix != None:
- self.thePrefix = prefix
- if self.theDataType == FILE:
- self.fName = fName
- if self.theDataType == FILE:
- self.data = data
- else:
- self.data = None
- self.close_silent = False
- self.parent = parent
- self.adc = 1
- self.secadc = 2
- self._single_adc_checked = False
- self.initUI()
- self.changePlotType(self.theType) # initially mark the correct button
- self.setWindowTitle(self.theName + " - " + plotList[type] + " - " + str(self.thePrefix))
- if self.theDataType == FILE:
- self.plot(type)
- else:
- live_plot_windows.addWindow(board_id, self)
- if board.get_board_status(board_id).last_file is not None:
- self.data = io.read_from_file(board.get_board_status(board_id).last_file)
- self.plot(type)
- def initUI(self):
- """
- Initialise the UI
- :return: -
- """
- self.plot_widget = SubPlotWidget(dType=self.theDataType)
- self.layout = QtGui.QVBoxLayout()
- self.plot_buttons_layout = QtGui.QHBoxLayout()
- self.heatmap_button = self.createButton(text=tr("Button", "Heatmap"), connect=lambda: self.plot(type=PlotType.Heatmap))
- self.fft_button = self.createButton(text=tr("Button", "FFT"), connect=lambda: self.plot(type=PlotType.FFT))
- self.trains_button = self.createButton(text=tr("Button", "Trains"), connect=lambda: self.plot(type=PlotType.Trains))
- self.combined_button = self.createButton(text=tr("Button", "Combined"), connect=lambda: self.plot(type=PlotType.Combined))
- self.compare_button = self.createButton(text=tr("Button", "Compare"), connect=lambda: self.plot(type=PlotType.Compare))
- self.type_buttons = {PlotType.Heatmap:self.heatmap_button, PlotType.FFT:self.fft_button,
- PlotType.Trains:self.trains_button, PlotType.Combined:self.combined_button,
- PlotType.Compare:self.compare_button}
- self.defaultButtonStyleSheet = self.heatmap_button.styleSheet()
- self.adc1 = self.createCheckbox(text="ADC 1", connect=self.change_adc)
- self.adc1.setChecked(True)
- self.adc2 = self.createCheckbox(text="ADC 2", connect=self.change_adc)
- self.adc3 = self.createCheckbox(text="ADC 3", connect=self.change_adc)
- self.adc4 = self.createCheckbox(text="ADC 4", connect=self.change_adc)
- self.plot_buttons_layout.addWidget(self.heatmap_button)
- self.plot_buttons_layout.addWidget(self.fft_button)
- self.plot_buttons_layout.addWidget(self.trains_button)
- self.plot_buttons_layout.addWidget(self.combined_button)
- self.plot_buttons_layout.addWidget(self.compare_button)
- self.adc_checkbox_layout = QtGui.QHBoxLayout()
- self.adc_checkbox_layout.addWidget(self.adc1)
- self.adc_checkbox_layout.addWidget(self.adc2)
- self.adc_checkbox_layout.addWidget(self.adc3)
- self.adc_checkbox_layout.addWidget(self.adc4)
- self.groupWidget = QtGui.QWidget()
- self.groupWidget.setLayout(self.adc_checkbox_layout)
- self.group = QtGui.QButtonGroup()
- self.group.addButton(self.adc1, 1)
- self.group.addButton(self.adc2, 2)
- self.group.addButton(self.adc3, 3)
- self.group.addButton(self.adc4, 4)
- self.group.setExclusive(True)
- self.compare_heading_left = self.createLabel("ADC 1")
- self.compare_heading_left.setAlignment(QtCore.Qt.AlignCenter)
- self.compare_heading_right = self.createLabel("ADC 2")
- self.compare_heading_right.setAlignment(QtCore.Qt.AlignCenter)
- self.compare_heading = QtGui.QWidget()
- self.compare_heading_layout = QtGui.QHBoxLayout()
- self.compare_heading_layout.addWidget(self.compare_heading_left)
- self.compare_heading_layout.addWidget(self.compare_heading_right)
- self.compare_heading.setLayout(self.compare_heading_layout)
- self.compare_heading.hide()
- self.adc1Compare = self.createCheckbox(text="ADC 1", connect=lambda: self.change_adc_compare(who=1))
- self.adc2Compare = self.createCheckbox(text="ADC 2", connect=lambda: self.change_adc_compare(who=2))
- self.adc3Compare = self.createCheckbox(text="ADC 3", connect=lambda: self.change_adc_compare(who=3))
- self.adc4Compare = self.createCheckbox(text="ADC 4", connect=lambda: self.change_adc_compare(who=4))
- self.adc1Compare.setChecked(True)
- self.adc2Compare.setChecked(True)
- self.adc_checkbox_compare_layout = QtGui.QHBoxLayout()
- self.adc_checkbox_compare_layout.addWidget(self.adc1Compare)
- self.adc_checkbox_compare_layout.addWidget(self.adc2Compare)
- self.adc_checkbox_compare_layout.addWidget(self.adc3Compare)
- self.adc_checkbox_compare_layout.addWidget(self.adc4Compare)
- self.groupWidgetCompare = QtGui.QWidget()
- self.groupWidgetCompare.setLayout(self.adc_checkbox_compare_layout)
- self.groupCompare = QtGui.QButtonGroup()
- self.groupCompare.setExclusive(False)
- self.groupCompare.addButton(self.adc1Compare, 1)
- self.groupCompare.addButton(self.adc2Compare, 2)
- self.groupCompare.addButton(self.adc3Compare, 3)
- self.groupCompare.addButton(self.adc4Compare, 4)
- self.groupWidgetCompare.hide()
- self.from_to_layout = QtGui.QHBoxLayout()
- self.from_spinbox = self.createSpinbox(0, 100000000, interval=100, connect=lambda: self.plot(self.theType))
- self.to_spinbox = self.createSpinbox(0, 100000000, start_value=1000, interval=100, connect=lambda: self.plot(self.theType))
- self.from_to_layout.addStretch()
- self.from_to_layout.addWidget(self.createLabel(tr("Label", "From:")))
- self.from_to_layout.addWidget(self.from_spinbox)
- self.from_to_layout.addWidget(self.createLabel(tr("Label", "To:")))
- self.from_to_layout.addWidget(self.to_spinbox)
- self.layout.addLayout(self.plot_buttons_layout)
- self.layout.addWidget(self.compare_heading)
- self.layout.addWidget(self.plot_widget)
- self.layout.addWidget(self.groupWidget)
- self.layout.addWidget(self.groupWidgetCompare)
- self.layout.addLayout(self.from_to_layout)
- self.setLayout(self.layout)
- def change_adc(self):
- """
- Change the adc for which data is plotted
- :return: -
- """
- self.adc = self.group.checkedId()
- self.change_identifier_text()
- self.plot(self.theType)
- def change_adc_compare(self, who):
- """
- Change the adcs displayed in a compare plot
- :return:
- """
- if not self._single_adc_checked:
- self.adc1Compare.setChecked(False)
- self.adc2Compare.setChecked(False)
- self.adc3Compare.setChecked(False)
- self.adc4Compare.setChecked(False)
- getattr(self, 'adc'+str(who)+'Compare').setChecked(True)
- self._single_adc_checked = True
- else:
- checked = [i.isChecked() for i in self.groupCompare.buttons()]
- self._single_adc_checked = False
- self.adc = np.where(np.array(checked) == True)[0][0] + 1 # +1 because adcs are 1 based and indices 0
- self.secadc = np.where(np.array(checked) == True)[0][1] + 1 # +1 because adcs are 1 based and indices 0
- self.compare_heading_left.setText("ADC "+str(self.adc))
- self.compare_heading_right.setText("ADC "+str(self.secadc))
- self.change_identifier_text()
- if self.theType in [ERROR, NO_DATA]:
- self.plot(self._old_type)
- else:
- self.plot(self.theType)
- def disable_buttons(self, b_bool):
- """
- Disable the buttons on this widget
- This is not used at the moment
- :param b_bool: (bool) disable(False) or enable(True)
- :return: -
- """
- self.heatmap_button.setDisabled(b_bool)
- self.fft_button.setDisabled(b_bool)
- self.trains_button.setDisabled(b_bool)
- self.combined_button.setDisabled(b_bool)
- def changePlotType(self, type):
- """
- Change the plot type to the given type.
- :param type: the new type
- """
- if type not in [ERROR, NO_DATA]:
- self._old_type = type
- self.theType = type
- self.plot_widget.changeType(type)
- for btype, button in self.type_buttons.iteritems():
- style = ""
- if btype == type:
- style += "QPushButton {background-color:lightgreen;}"
- else:
- style += self.defaultButtonStyleSheet
- style += "QPushButton:focus{background-color: lightgrey!important; border-color: lightblue;}" # Note: this does not work
- button.setStyleSheet(style)
- def plot(self, type=None):
- """
- Wrapper function to call the correct plot function depending on type
- :param type: (int) the plot type
- :return: -
- """
- try:
- if type == None:
- if self.theType in [ERROR, NO_DATA]:
- type = self._old_type
- else:
- type = self.theType
- self.do_plot(type)
- except Exception:
- if self.data is None or len(self.data.array) == 0:
- self.changePlotType(NO_DATA)
- else:
- self.changePlotType(ERROR)
- self.plot_widget.plot(None)
- def plot_live(self, type=None, data=None):
- """
- Function to call when livedata is to be plotted
- :param type: (int) plot type
- :param data: (dataset.DataSet) data to plot
- :return: -
- """
- self.data = data
- try:
- if type == None:
- if self.theType in [ERROR, NO_DATA]:
- type = self._old_type
- else:
- type = self.theType
- if type == PlotType.Trains or type == PlotType.Combined:
- self.do_plot(type, autorange=True)
- else:
- self.do_plot(type, autorange=False)
- except Exception:
- if len(self.data.array) == 0:
- self.changePlotType(NO_DATA)
- else:
- self.changePlotType(ERROR)
- self.plot_widget.plot(None)
- def do_plot(self, type, autorange=True):
- """
- Actually perform a plot (this calls SubPlotWidget.plot)
- :param type: (int) plot type
- :param autorange: (bool) whether to perform a autorange upon plot
- :return: -
- """
- if self.theType != type:
- if type == PlotType.Combined:
- self.groupWidget.hide()
- elif self.theType == PlotType.Combined:
- self.groupWidget.show()
- if type == PlotType.Compare:
- self.groupWidget.hide()
- self.groupWidgetCompare.show()
- self.compare_heading.show()
- else:
- self.groupWidget.show()
- self.groupWidgetCompare.hide()
- self.compare_heading.hide()
- self.changePlotType(type)
- self.setWindowTitle(self.theName + " - " + plotList[type] + " - " + str(self.thePrefix))
- self.change_identifier_text()
- if self.theType == PlotType.Compare:
- self.change_identifier_text()
- if self.theType == PlotType.Compare:
- self.groupWidget.hide()
- self.groupWidgetCompare.show()
- self.compare_heading.show()
- else:
- self.groupWidgetCompare.hide()
- self.compare_heading.hide()
- f = self.from_spinbox.value()
- t = self.to_spinbox.value() if self.to_spinbox.value() > f else f+1
- if type == PlotType.FFT:
- self.plot_widget.plot(np.abs(self.data.fft(adc=self.adc, frm=f, to=t+1, drop_first_bin=True)).transpose(),
- autorange=autorange,
- xvalueborders=[self.data.fft_freq_dist(), self.data.fft_max_freq()])
- if type == PlotType.Heatmap:
- self.plot_widget.plot(self.data.heatmap(adc=self.adc, frm=f, to=t).transpose(), autorange=autorange)
- if type == PlotType.Compare:
- self.plot_widget.plot([self.data.heatmap(adc=self.adc, frm=f, to=t).transpose(),self.data.heatmap(adc=self.secadc, frm=f, to=t).transpose()], autorange=autorange)
- if type == PlotType.Trains:
- self.plot_widget.plot(self.data.train(adc=self.adc, frm=f, to=t), autorange=autorange)
- if type == PlotType.Combined:
- self.plot_widget.plot(self.data.combined(frm=f, to=t), autorange=autorange)
- def change_identifier_text(self):
- """
- Change the text that identifies the plot in the left bar
- :return:
- """
- if self.theType is PlotType.Compare:
- the_text = str(self.adc)+"+"+str(self.secadc)
- else:
- the_text = str(self.adc)
- if self.theType in [ERROR, NO_DATA]:
- type = self._old_type
- else:
- type = self.theType
- if self.theDataType is LIVE:
- self.change_type_signal.emit(self.theId, type,
- the_text+" B: "+available_boards.get_board_name_from_id(self.board_id))
- else:
- self.change_type_signal.emit(self.theId, type, the_text)
- def closeEvent(self, event):
- """
- Event Handler to handle the event of closing this window
- :param event: QEvent
- :return: -
- """
- if not self.close_silent:
- if not self.parent.remove_plot(self.theId, silent=self.close_silent):
- event.ignore()
- return
- del self.data
- del self.plot_widget
- super(PlotWidget, self).closeEvent(event)
- if self.theDataType == LIVE:
- live_plot_windows.removeWindow(self.board_id, self)
- self.close_signal.emit()
- del self
|