plotWidget.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848
  1. """
  2. This Module implements the Plot Windows used in KCG
  3. """
  4. import pyqtgraph as pg
  5. pg.setConfigOption('useOpenGl', True)
  6. pg.setConfigOption('background', 'w')
  7. pg.setConfigOption('foreground', 'k')
  8. import numpy as np
  9. from PyQt4 import QtCore, QtGui
  10. from groupedelements import live_plot_windows
  11. from backend import io
  12. from backend import board
  13. from backend.board import available_boards
  14. import kcgwidget as kcgw
  15. tr = kcgw.tr
  16. LIVE = 1
  17. FILE = 2
  18. ERROR = 99
  19. NO_DATA = 98
  20. class Enum():
  21. """
  22. Simple Enum Class (as this is not supported by clean python)
  23. """
  24. def __init__(self, *args):
  25. self.idx = 0
  26. for i in args:
  27. setattr(self, i, self.idx)
  28. self.idx += 1
  29. plotList = [tr("Label", "Heatmap"), tr("Label", "FFT"), tr("Label", "Trains"), tr("Label", "Combined"), tr("Label", "Compare")]
  30. PlotType = Enum(*[str(i) for i in plotList])
  31. # gradient = {'mode': 'rgb',
  32. # 'ticks': [(0.0, (0.37281, 0.11883, 3.53583, 255)),
  33. # (0.0390625, (4.9401150000000005, 3.858915, 22.635585, 255)),
  34. # (0.078125, (15.6417, 9.330449999999999, 45.29871, 255)),
  35. # (0.1171875, (29.74728, 12.13137, 69.44185499999999, 255)),
  36. # (0.15625, (46.774395000000005, 10.283895, 90.51760499999999, 255)),
  37. # (0.1953125, (64.1631, 9.614775, 102.86139, 255)),
  38. # (0.234375, (80.65191, 13.63995, 108.40458, 255)),
  39. # (0.2734375, (96.64525499999999, 19.444515, 110.343345, 255)),
  40. # (0.3125, (112.507785, 25.33119, 110.05646999999999, 255)),
  41. # (0.3515625, (128.390715, 31.001625, 107.95578, 255)),
  42. # (0.390625, (144.29277, 36.609585, 104.10579, 255)),
  43. # (0.4296875, (160.100985, 42.476625, 98.50038, 255)),
  44. # (0.46875, (175.606515, 49.020945, 91.188765, 255)),
  45. # (0.5078125, (190.517385, 56.70639, 82.32827999999999, 255)),
  46. # (0.546875, (204.477105, 65.96187, 72.190245, 255)),
  47. # (0.5859375, (217.10292, 77.07629999999999, 61.10718, 255)),
  48. # (0.625, (228.047775, 90.11674500000001, 49.36392, 255)),
  49. # (0.6640625, (237.05922, 104.927145, 37.068585, 255)),
  50. # (0.703125, (243.99726, 121.21578, 24.147225, 255)),
  51. # (0.7421875, (248.797635, 138.66849, 11.122589999999999, 255)),
  52. # (0.78125, (251.41776000000002, 157.01625, 6.52596, 255)),
  53. # (0.8203125, (251.80332, 176.04333, 20.397450000000003, 255)),
  54. # (0.859375, (249.90816, 195.543435, 42.420015, 255)),
  55. # (0.8984375, (245.92047, 215.18124, 69.714705, 255)),
  56. # (0.9375, (241.63647, 233.936745, 104.719575, 255)),
  57. # (0.9765625, (245.26206, 248.86062, 145.840875, 255)),
  58. # (0.99609375, (252.03231, 254.58282, 164.45562, 255))]}
  59. gradient = {'mode': 'rgb',
  60. 'ticks': [(0.0, (0.37281, 0.11883, 3.53583, 255)),
  61. (0.1171875, (29.74728, 12.13137, 69.44185499999999, 255)),
  62. (0.234375, (80.65191, 13.63995, 108.40458, 255)),
  63. (0.3515625, (128.390715, 31.001625, 107.95578, 255)),
  64. (0.46875, (175.606515, 49.020945, 91.188765, 255)),
  65. (0.5859375, (217.10292, 77.07629999999999, 61.10718, 255)),
  66. (0.703125, (243.99726, 121.21578, 24.147225, 255)),
  67. (0.8203125, (251.80332, 176.04333, 20.397450000000003, 255)),
  68. (0.9375, (241.63647, 233.936745, 104.719575, 255)),
  69. (0.99609375, (252.03231, 254.58282, 164.45562, 255))]}
  70. class CustomGradientEditorItem(pg.GradientEditorItem):
  71. def __init__(self, **kwargs):
  72. pg.GradientEditorItem.__init__(self, **kwargs)
  73. self.customGradients = {}
  74. def addGrad(self, name, grad_dic):
  75. if name not in self.customGradients:
  76. self.customGradients[name] = grad_dic
  77. px = QtGui.QPixmap(100, 15)
  78. p = QtGui.QPainter(px)
  79. self.restoreState(grad_dic)
  80. length_backup = self.length
  81. self.length = 100
  82. grad = self.getGradient()
  83. self.length = length_backup
  84. brush = QtGui.QBrush(grad)
  85. p.fillRect(QtCore.QRect(0, 0, 100, 15), brush)
  86. p.end()
  87. label = QtGui.QLabel()
  88. label.setPixmap(px)
  89. label.setContentsMargins(1, 1, 1, 1)
  90. act = QtGui.QWidgetAction(self)
  91. act.setDefaultWidget(label)
  92. act.triggered.connect(self.contextMenuClicked)
  93. act.name = name
  94. if len(self.customGradients) > 1:
  95. self.menu.insertAction(self.menu.actions()[0], act)
  96. else:
  97. sep = self.menu.insertSeparator(self.menu.actions()[0])
  98. self.menu.insertAction(sep, act)
  99. self.restoreState(grad_dic)
  100. def restoreState(self, state):
  101. self.setColorMode(state['mode'])
  102. for t in list(self.ticks.keys()):
  103. self.removeTick(t, finish=False)
  104. self.bottomTick = [None, 10]
  105. self.topmostTick = [None, -1]
  106. for t in state['ticks']:
  107. c = QtGui.QColor(*t[1])
  108. tick = self.addTick(t[0], c, finish=False)
  109. if t[0] < self.bottomTick[1]:
  110. self.bottomTick = [tick, t[0]]
  111. if t[0] > self.topmostTick[1]:
  112. self.topmostTick = [tick, t[0]]
  113. self.tickPosInPercent = self.ticks.copy()
  114. for t in self.ticks:
  115. if t is self.topmostTick[0] or t is self.bottomTick[0]:
  116. continue
  117. t.hide()
  118. t.movable = False
  119. # self.bottomTick[1] *= self.length
  120. # self.topmostTick[1] *= self.length
  121. self.updateGradient()
  122. self.sigGradientChangeFinished.emit(self)
  123. def loadPreset(self, name):
  124. if name in self.customGradients:
  125. self.restoreState(self.customGradients[name])
  126. else:
  127. super(CustomGradientEditorItem, self).loadPreset(name)
  128. def tickMoved(self, tick, pos):
  129. if tick is self.bottomTick[0]:
  130. # self.bottomTick[1] = pos.x()
  131. pg.TickSliderItem.tickMoved(self, tick, pos)
  132. elif tick is self.topmostTick[0]:
  133. # self.topmostTick[1] = pos.x()
  134. pg.TickSliderItem.tickMoved(self, tick, pos)
  135. if tick in [self.bottomTick[0], self.topmostTick[0]]: # if bottom or topmost tick was moved
  136. if pos.x() < self.length and pos.x() > 0: # if tick is not at top or bottom
  137. newUnit = (self.topmostTick[0].pos().x() - self.bottomTick[0].pos().x()) # create new virtual unit length
  138. for t in self.ticks: # for every tick
  139. if t not in [self.bottomTick[0], self.topmostTick[0]]: # if tick is not bottom or topmost tick
  140. pos = t.pos()
  141. new_x = self.bottomTick[0].pos().x() + self.tickPosInPercent[t] * newUnit
  142. pos.setX(new_x)
  143. t.setPos(pos)
  144. pg.TickSliderItem.tickMoved(self, t, pos)
  145. self.updateGradient()
  146. class SpectrogramColorLegendItem(pg.GraphicsWidget):
  147. """
  148. The Item used as Legend for Heatmap and FFT Plot
  149. """
  150. gradientChanged = QtCore.pyqtSignal()
  151. def __init__(self, img=None):
  152. """
  153. Initialise the Legend
  154. :param img: (ImageItem) the image item this is the legend for
  155. :return: -
  156. """
  157. super(SpectrogramColorLegendItem, self).__init__()
  158. self.layout = QtGui.QGraphicsGridLayout()
  159. self.setLayout(self.layout)
  160. self.img = img
  161. # self.gei = pg.GradientEditorItem(orientation='right')
  162. self.gei = CustomGradientEditorItem(orientation='right', allowAdd=False)
  163. self.legend_axis = pg.AxisItem(orientation='left')
  164. # self.addItem(self.legend_axis)
  165. self.layout.addItem(self.legend_axis, 0, 0, alignment=QtCore.Qt.AlignVCenter)
  166. # self.addItem(self.gei)
  167. self.layout.addItem(self.gei, 0, 1)
  168. self.gei.sigGradientChanged.connect(self.gradient_changed)
  169. # self.gei.loadPreset('spectrum')
  170. self.gei.addGrad('inferno', gradient)
  171. # self.setFixedWidth(80)
  172. self.image_changed()
  173. def gradient_changed(self):
  174. """
  175. Proxy function for the gradient_cnahged event of the GradientEditorItem
  176. :return:
  177. """
  178. self.gradientChanged.emit()
  179. def set_image(self, img):
  180. """
  181. Set the Image this is the legend for (only needed if not already done upon initialisation)
  182. :param img: (ImageItem) The image item to use
  183. :return: -
  184. """
  185. self.img = img
  186. self.image_changed(True)
  187. def update_axis(self):
  188. """
  189. Update the axis of this legend
  190. :return: -
  191. """
  192. if self.img.getLevels():
  193. # min, max = self.img.getLevels()
  194. min, max = (np.min(self.img.image), np.max(self.img.image))
  195. self.legend_axis.setRange(min, max)
  196. def reset_gradient(self):
  197. """
  198. Reset the gradient to the preset "spectrum"
  199. :return: -
  200. """
  201. # self.gei.loadPreset('spectrum')
  202. self.gei.loadPreset('inferno')
  203. # self.gei.restoreState(gradient)
  204. def image_changed(self, reset_gradient=True):
  205. """
  206. Call this to adjust the legend when the image has changed
  207. :param reset_gradient: (bool) whether to reset the gradient or not
  208. :return:
  209. """
  210. if self.img:
  211. self.update_axis()
  212. if reset_gradient:
  213. self.reset_gradient()
  214. def getLookupTable(self, nPts, alpha=None):
  215. """
  216. Get the look up table of the imageitem
  217. :param nPts: (int) number of points the lookup table is defined on
  218. :param alpha: ??
  219. :return: -
  220. """
  221. return self.gei.getLookupTable(nPts, alpha=alpha)
  222. def resizeEvent(self, event):
  223. """
  224. Handle resizing of the window
  225. """
  226. self.legend_axis.setHeight(self.gei.length)
  227. class SubPlotWidget(pg.GraphicsLayoutWidget):
  228. """
  229. The Widget actually containing the plots and images
  230. """
  231. def __init__(self, dType=FILE):
  232. super(SubPlotWidget, self).__init__()
  233. self.dType = dType
  234. self._type_changed = False
  235. # self.setStyleSheet("border: 5px solid black; margin: 10px;")
  236. self.plotType = 1
  237. self.plotItem = pg.PlotItem()
  238. self.img = pg.ImageItem()
  239. self.plotItem2 = pg.PlotItem()
  240. self.img2 = pg.ImageItem()
  241. self.plotItem2.addItem(self.img2)
  242. self.setFrameStyle(self.StyledPanel | self.Sunken)
  243. self.gradient_legend = SpectrogramColorLegendItem(self.img)
  244. def changelut():
  245. """ Handle changing of the lookup table"""
  246. self.img.setLookupTable(self.gradient_legend.getLookupTable(512))
  247. self.img2.setLookupTable(self.gradient_legend.getLookupTable(512))
  248. self.gradient_legend.gradientChanged.connect(changelut)
  249. self.addItem(self.plotItem)
  250. self.plotItem.addItem(self.img)
  251. self.plotItemPlotScatter = pg.PlotDataItem(pen=None, symbolPen=pg.mkPen(50, 50, 255), symbolSize=3, pxMode=True, symbolBrush=pg.mkBrush(50, 50, 255), downsample=200)
  252. self.plotItemPlotScatter1 = pg.PlotDataItem(pen=None, symbolPen=pg.mkPen(255, 50, 50), symbolSize=3, pxMode=True, symbolBrush=pg.mkBrush(255, 50, 50), downsample=200)
  253. # self.plotItemPlotScatter2 = pg.PlotDataItem(pen=None, symbolPen=pg.mkPen(50, 255, 50), symbolSize=3, pxMode=True, symbolBrush=pg.mkBrush(50, 255, 50), downsample=200)
  254. self.plotItemPlot = pg.PlotDataItem() # pen="#000000")
  255. self.plotItem.addItem(self.plotItemPlot)
  256. self.plotItem.addItem(self.plotItemPlotScatter)
  257. self.plotItem.addItem(self.plotItemPlotScatter1)
  258. # self.plotItem.addItem(self.plotItemPlotScatter2)
  259. self.plotItem.vb.origAutoRange = self.plotItem.vb.autoRange
  260. self.error_label = pg.LabelItem("Error during plot.", color=(200, 50, 50))
  261. self.error_label.hide()
  262. self.error_label.scale(1, -0.6)
  263. self.error_label.translate(0, -30)
  264. self.plotItem.addItem(self.error_label)
  265. self.no_data_label = pg.LabelItem("No data to plot.", color=(200, 50, 50))
  266. self.no_data_label.hide()
  267. self.no_data_label.scale(1, -0.6)
  268. self.no_data_label.translate(0, -30)
  269. self.plotItem.addItem(self.no_data_label)
  270. def _enableCustomAutoRange(self, data):
  271. """
  272. Enable custom auto range in this plot
  273. :param data: the data to autorange
  274. :return:
  275. """
  276. def newAutoRange(*args, **kwargs):
  277. """ function to handle the new autorange """
  278. bounds = [np.min(data), np.max(data)]
  279. self.plotItem.vb.setRange(xRange=[0, len(data)],
  280. yRange=[bounds[0]-0.1*(bounds[1]-bounds[0])-1, bounds[1]+0.1*(bounds[1]-bounds[0])+1], update=True)
  281. self.plotItem.update()
  282. self.plotItem.vb.autoRange = newAutoRange
  283. self.plotItem.autoBtn.clicked.disconnect()
  284. self.plotItem.autoBtn.clicked.connect(newAutoRange)
  285. def _disableCustomAutoRange(self):
  286. """
  287. Disable the custom autorange and reset it to default
  288. :return: -
  289. """
  290. self.plotItem.vb.autoRange = self.plotItem.vb.origAutoRange
  291. self.plotItem.autoBtn.clicked.disconnect()
  292. self.plotItem.autoBtn.clicked.connect(self.plotItem.autoBtnClicked)
  293. @QtCore.pyqtSlot(np.ndarray, tuple, tuple)
  294. def plot(self, data, xvalueborders=None, yvalueborders=None, autorange=True):
  295. """
  296. Plot Data. The plot type depends on the type property
  297. :param data: (dataset.DataSet) data to plot
  298. :param xvalueborders: (touple) the borders for the xvalues
  299. :param yvalueborders: (touple) the borders for the yvalues
  300. :param autorange: (bool) whether to perform a autorange or not
  301. :return: -
  302. """
  303. self.error_label.hide()
  304. self.no_data_label.hide()
  305. self.plotItemPlot.clear()
  306. self.plotItemPlotScatter.clear()
  307. self.plotItemPlotScatter1.clear()
  308. # self.plotItemPlotScatter2.clear()
  309. self.plotItem.resetTransform()
  310. self.plotItem.getAxis('bottom').setScale()
  311. self.img.show()
  312. if self._type_changed:
  313. self.plotItem.autoBtnClicked()
  314. try:
  315. self.removeItem(self.plotItem2)
  316. except Exception as e:
  317. if "Could not determine index of item" in str(e):
  318. pass
  319. else:
  320. raise
  321. if self.plotType == PlotType.FFT or self.plotType == PlotType.Heatmap:
  322. self.addItem(self.gradient_legend)
  323. self.gradient_legend.show()
  324. self.img.setImage(data, scale=[1, 1/1000.])
  325. self.img.resetTransform()
  326. self.img.setAutoDownsample(True)
  327. if autorange:
  328. self.gradient_legend.image_changed()
  329. if xvalueborders:
  330. self.plotItem.getAxis('bottom').setScale((xvalueborders[1]-xvalueborders[0])/float(data.shape[0]))
  331. self.img.translate(xvalueborders[0]/self.plotItem.getAxis('bottom').scale, 0)
  332. else:
  333. self.plotItem.getAxis('bottom').setScale()
  334. if yvalueborders:
  335. self.img.translate(0, yvalueborders[0])
  336. self.plotItem.getAxis('left').setScale(yvalueborders[1]/float(data.shape[1]))
  337. self._disableCustomAutoRange()
  338. self.plotItem.setClipToView(True)
  339. if self.plotType == PlotType.Compare:
  340. self.addItem(self.plotItem2)
  341. self.plotItem2.vb.linkView(self.plotItem2.vb.XAxis, self.plotItem.vb)
  342. self.plotItem2.vb.linkView(self.plotItem2.vb.YAxis, self.plotItem.vb)
  343. self.addItem(self.gradient_legend)
  344. self.gradient_legend.show()
  345. self.img.setImage(data[0], scale=[1, 1/1000.])
  346. self.img.show()
  347. self.img.resetTransform()
  348. self.img2.setImage(data[1], scale=[1, 1/1000.])
  349. self.img2.show()
  350. self.img2.resetTransform()
  351. self.img.setAutoDownsample(True)
  352. self.img2.setAutoDownsample(True)
  353. if autorange:
  354. self.gradient_legend.image_changed()
  355. pass
  356. if xvalueborders:
  357. self.plotItem.getAxis('bottom').setScale((xvalueborders[1]-xvalueborders[0])/float(data[0].shape[0]))
  358. self.plotItem2.getAxis('bottom').setScale((xvalueborders[1]-xvalueborders[0])/float(data[1].shape[0]))
  359. self.img.translate(xvalueborders[0]/self.plotItem.getAxis('bottom').scale, 0)
  360. self.img2.translate(xvalueborders[0]/self.plotItem.getAxis('bottom').scale, 0)
  361. else:
  362. self.plotItem.getAxis('bottom').setScale()
  363. self.plotItem2.getAxis('bottom').setScale()
  364. if yvalueborders:
  365. self.img.translate(0, yvalueborders[0])
  366. self.img2.translate(0, yvalueborders[0])
  367. self.plotItem.getAxis('left').setScale(yvalueborders[1]/float(data[0].shape[1]))
  368. self.plotItem2.getAxis('left').setScale(yvalueborders[1]/float(data[1].shape[1]))
  369. self._disableCustomAutoRange()
  370. self.plotItem.setClipToView(True)
  371. if self.plotType == PlotType.Trains:
  372. self.img.clear()
  373. self.plotItemPlot.show()
  374. self.gradient_legend.hide()
  375. try:
  376. self.removeItem(self.gradient_legend)
  377. pass
  378. except Exception, e:
  379. if not "Could not determine index of item" in str(e):
  380. raise
  381. self.plotItemPlotScatter.clear()
  382. self.plotItemPlotScatter1.clear()
  383. self.plotItemPlot.setData(data)
  384. self._enableCustomAutoRange(data)
  385. if self.plotType == PlotType.Combined:
  386. self.img.clear()
  387. self.plotItemPlotScatter.show()
  388. self.plotItemPlotScatter1.show()
  389. self.gradient_legend.hide()
  390. try:
  391. self.removeItem(self.gradient_legend)
  392. pass
  393. except Exception, e:
  394. if not "Could not determine index of item" in str(e):
  395. raise
  396. self.plotItemPlot.clear()
  397. self.plotItemPlotScatter.setData(data[0].transpose())
  398. self.plotItemPlotScatter1.setData(data[1].transpose())
  399. self._enableCustomAutoRange(data[0][1])
  400. self.plotItem.setClipToView(False) # NOTE: otherwise only a very small portion of data is visible :wonder:
  401. if self.plotType == ERROR or self.plotType == NO_DATA:
  402. self.img.hide()
  403. self.gradient_legend.hide()
  404. self.img2.hide()
  405. self.plotItemPlot.hide()
  406. self.plotItemPlotScatter.hide()
  407. self.plotItemPlotScatter1.hide()
  408. if self.plotType == ERROR:
  409. self.error_label.show()
  410. else:
  411. self.no_data_label.show()
  412. if autorange:
  413. self.plotItem.getViewBox().autoRange()
  414. self.plotItem.getViewBox().autoRange()
  415. self.labelAxes()
  416. self.gradient_legend.update_axis()
  417. def labelAxes(self):
  418. """
  419. Add Labels to the axis depending on the self.plotType property.
  420. :return: -
  421. """
  422. if self.plotType == PlotType.Heatmap:
  423. self.plotItem.setLabel('bottom', 'Turn', '')
  424. self.plotItem.setLabel('left', 'Bunch Position', '')
  425. elif self.plotType == PlotType.FFT:
  426. self.plotItem.setLabel('left', 'Bunch Position', '')
  427. self.plotItem.setLabel('bottom', 'Frequency', 'Hz')
  428. elif self.plotType == PlotType.Trains:
  429. self.plotItem.setLabel('left', '', '')
  430. self.plotItem.setLabel('bottom', 'Sample Point', '')
  431. elif self.plotType == PlotType.Combined:
  432. self.plotItem.setLabel('left', '', '')
  433. self.plotItem.setLabel('bottom', '', '')
  434. if self.plotType == PlotType.Compare:
  435. self.plotItem.setLabel('bottom', 'Turn', '')
  436. self.plotItem.setLabel('left', 'Bunch Position', '')
  437. self.plotItem2.setLabel('bottom', 'Turn', '')
  438. self.plotItem2.setLabel('left', 'Bunch Position', '')
  439. def changeType(self, type):
  440. """
  441. Change the plot Type
  442. :param type: (int) the new type
  443. :return: -
  444. """
  445. if type != self.plotType:
  446. self._type_changed = True
  447. self.plotType = type
  448. self.gradient_legend.image_changed()
  449. class PlotWidget(kcgw.KCGWidgets):
  450. """
  451. The container Class holding various buttons and controls and the actual plots as SubPlotWidgets instance
  452. """
  453. close_signal = QtCore.pyqtSignal()
  454. change_type_signal = QtCore.pyqtSignal(int, int, str)
  455. def __init__(self, board_id, parent=None, name=None, unique_id=None, type=None, datatype=None, prefix=None, fName=None, data=None):
  456. """
  457. Initialise the Plot Widgt
  458. :param parent: (QWidget) the parent widget
  459. :param name: (str) name of this widget
  460. :param unique_id: (int) unique id of this widget
  461. :param type: (int) type of this widget
  462. :param datatype: (int) datatype (LIVE or FILE)
  463. :param prefix: (str) prefix for text in the listview in LeftBar
  464. :param fName: (str) the filename (this is only used if datatype is FILE)
  465. :param data: (dataset.DataSet) the data to be plotted
  466. :return: -
  467. """
  468. super(PlotWidget, self).__init__()
  469. self.board_id = board_id
  470. if name != None:
  471. self.theName = name
  472. if unique_id != None:
  473. self.theId = unique_id
  474. if type != None:
  475. self.theType = type
  476. self._old_type = type
  477. if datatype != None:
  478. self.theDataType = datatype
  479. if prefix != None:
  480. self.thePrefix = prefix
  481. if self.theDataType == FILE:
  482. self.fName = fName
  483. if self.theDataType == FILE:
  484. self.data = data
  485. else:
  486. self.data = None
  487. self.close_silent = False
  488. self.parent = parent
  489. self.adc = 1
  490. self.secadc = 2
  491. self._single_adc_checked = False
  492. self.initUI()
  493. self.changePlotType(self.theType) # initially mark the correct button
  494. self.setWindowTitle(self.theName + " - " + plotList[type] + " - " + str(self.thePrefix))
  495. if self.theDataType == FILE:
  496. self.plot(type)
  497. else:
  498. live_plot_windows.addWindow(board_id, self)
  499. if board.get_board_status(board_id).last_file is not None:
  500. self.data = io.read_from_file(board.get_board_status(board_id).last_file)
  501. self.plot(type)
  502. def initUI(self):
  503. """
  504. Initialise the UI
  505. :return: -
  506. """
  507. self.plot_widget = SubPlotWidget(dType=self.theDataType)
  508. self.layout = QtGui.QVBoxLayout()
  509. self.plot_buttons_layout = QtGui.QHBoxLayout()
  510. self.heatmap_button = self.createButton(text=tr("Button", "Heatmap"), connect=lambda: self.plot(type=PlotType.Heatmap))
  511. self.fft_button = self.createButton(text=tr("Button", "FFT"), connect=lambda: self.plot(type=PlotType.FFT))
  512. self.trains_button = self.createButton(text=tr("Button", "Trains"), connect=lambda: self.plot(type=PlotType.Trains))
  513. self.combined_button = self.createButton(text=tr("Button", "Combined"), connect=lambda: self.plot(type=PlotType.Combined))
  514. self.compare_button = self.createButton(text=tr("Button", "Compare"), connect=lambda: self.plot(type=PlotType.Compare))
  515. self.type_buttons = {PlotType.Heatmap:self.heatmap_button, PlotType.FFT:self.fft_button,
  516. PlotType.Trains:self.trains_button, PlotType.Combined:self.combined_button,
  517. PlotType.Compare:self.compare_button}
  518. self.defaultButtonStyleSheet = self.heatmap_button.styleSheet()
  519. self.adc1 = self.createCheckbox(text="ADC 1", connect=self.change_adc)
  520. self.adc1.setChecked(True)
  521. self.adc2 = self.createCheckbox(text="ADC 2", connect=self.change_adc)
  522. self.adc3 = self.createCheckbox(text="ADC 3", connect=self.change_adc)
  523. self.adc4 = self.createCheckbox(text="ADC 4", connect=self.change_adc)
  524. self.plot_buttons_layout.addWidget(self.heatmap_button)
  525. self.plot_buttons_layout.addWidget(self.fft_button)
  526. self.plot_buttons_layout.addWidget(self.trains_button)
  527. self.plot_buttons_layout.addWidget(self.combined_button)
  528. self.plot_buttons_layout.addWidget(self.compare_button)
  529. self.adc_checkbox_layout = QtGui.QHBoxLayout()
  530. self.adc_checkbox_layout.addWidget(self.adc1)
  531. self.adc_checkbox_layout.addWidget(self.adc2)
  532. self.adc_checkbox_layout.addWidget(self.adc3)
  533. self.adc_checkbox_layout.addWidget(self.adc4)
  534. self.groupWidget = QtGui.QWidget()
  535. self.groupWidget.setLayout(self.adc_checkbox_layout)
  536. self.group = QtGui.QButtonGroup()
  537. self.group.addButton(self.adc1, 1)
  538. self.group.addButton(self.adc2, 2)
  539. self.group.addButton(self.adc3, 3)
  540. self.group.addButton(self.adc4, 4)
  541. self.group.setExclusive(True)
  542. self.compare_heading_left = self.createLabel("ADC 1")
  543. self.compare_heading_left.setAlignment(QtCore.Qt.AlignCenter)
  544. self.compare_heading_right = self.createLabel("ADC 2")
  545. self.compare_heading_right.setAlignment(QtCore.Qt.AlignCenter)
  546. self.compare_heading = QtGui.QWidget()
  547. self.compare_heading_layout = QtGui.QHBoxLayout()
  548. self.compare_heading_layout.addWidget(self.compare_heading_left)
  549. self.compare_heading_layout.addWidget(self.compare_heading_right)
  550. self.compare_heading.setLayout(self.compare_heading_layout)
  551. self.compare_heading.hide()
  552. self.adc1Compare = self.createCheckbox(text="ADC 1", connect=lambda: self.change_adc_compare(who=1))
  553. self.adc2Compare = self.createCheckbox(text="ADC 2", connect=lambda: self.change_adc_compare(who=2))
  554. self.adc3Compare = self.createCheckbox(text="ADC 3", connect=lambda: self.change_adc_compare(who=3))
  555. self.adc4Compare = self.createCheckbox(text="ADC 4", connect=lambda: self.change_adc_compare(who=4))
  556. self.adc1Compare.setChecked(True)
  557. self.adc2Compare.setChecked(True)
  558. self.adc_checkbox_compare_layout = QtGui.QHBoxLayout()
  559. self.adc_checkbox_compare_layout.addWidget(self.adc1Compare)
  560. self.adc_checkbox_compare_layout.addWidget(self.adc2Compare)
  561. self.adc_checkbox_compare_layout.addWidget(self.adc3Compare)
  562. self.adc_checkbox_compare_layout.addWidget(self.adc4Compare)
  563. self.groupWidgetCompare = QtGui.QWidget()
  564. self.groupWidgetCompare.setLayout(self.adc_checkbox_compare_layout)
  565. self.groupCompare = QtGui.QButtonGroup()
  566. self.groupCompare.setExclusive(False)
  567. self.groupCompare.addButton(self.adc1Compare, 1)
  568. self.groupCompare.addButton(self.adc2Compare, 2)
  569. self.groupCompare.addButton(self.adc3Compare, 3)
  570. self.groupCompare.addButton(self.adc4Compare, 4)
  571. self.groupWidgetCompare.hide()
  572. self.from_to_layout = QtGui.QHBoxLayout()
  573. self.from_spinbox = self.createSpinbox(0, 100000000, interval=100, connect=lambda: self.plot(self.theType))
  574. self.to_spinbox = self.createSpinbox(0, 100000000, start_value=1000, interval=100, connect=lambda: self.plot(self.theType))
  575. self.from_to_layout.addStretch()
  576. self.from_to_layout.addWidget(self.createLabel(tr("Label", "From:")))
  577. self.from_to_layout.addWidget(self.from_spinbox)
  578. self.from_to_layout.addWidget(self.createLabel(tr("Label", "To:")))
  579. self.from_to_layout.addWidget(self.to_spinbox)
  580. self.layout.addLayout(self.plot_buttons_layout)
  581. self.layout.addWidget(self.compare_heading)
  582. self.layout.addWidget(self.plot_widget)
  583. self.layout.addWidget(self.groupWidget)
  584. self.layout.addWidget(self.groupWidgetCompare)
  585. self.layout.addLayout(self.from_to_layout)
  586. self.setLayout(self.layout)
  587. def change_adc(self):
  588. """
  589. Change the adc for which data is plotted
  590. :return: -
  591. """
  592. self.adc = self.group.checkedId()
  593. self.change_identifier_text()
  594. self.plot(self.theType)
  595. def change_adc_compare(self, who):
  596. """
  597. Change the adcs displayed in a compare plot
  598. :return:
  599. """
  600. if not self._single_adc_checked:
  601. self.adc1Compare.setChecked(False)
  602. self.adc2Compare.setChecked(False)
  603. self.adc3Compare.setChecked(False)
  604. self.adc4Compare.setChecked(False)
  605. getattr(self, 'adc'+str(who)+'Compare').setChecked(True)
  606. self._single_adc_checked = True
  607. else:
  608. checked = [i.isChecked() for i in self.groupCompare.buttons()]
  609. self._single_adc_checked = False
  610. self.adc = np.where(np.array(checked) == True)[0][0] + 1 # +1 because adcs are 1 based and indices 0
  611. self.secadc = np.where(np.array(checked) == True)[0][1] + 1 # +1 because adcs are 1 based and indices 0
  612. self.compare_heading_left.setText("ADC "+str(self.adc))
  613. self.compare_heading_right.setText("ADC "+str(self.secadc))
  614. self.change_identifier_text()
  615. if self.theType in [ERROR, NO_DATA]:
  616. self.plot(self._old_type)
  617. else:
  618. self.plot(self.theType)
  619. def disable_buttons(self, b_bool):
  620. """
  621. Disable the buttons on this widget
  622. This is not used at the moment
  623. :param b_bool: (bool) disable(False) or enable(True)
  624. :return: -
  625. """
  626. self.heatmap_button.setDisabled(b_bool)
  627. self.fft_button.setDisabled(b_bool)
  628. self.trains_button.setDisabled(b_bool)
  629. self.combined_button.setDisabled(b_bool)
  630. def changePlotType(self, type):
  631. if type not in [ERROR, NO_DATA]:
  632. self._old_type = type
  633. self.theType = type
  634. self.plot_widget.changeType(type)
  635. for btype, button in self.type_buttons.iteritems():
  636. style = ""
  637. if btype == type:
  638. style += "QPushButton {background-color:lightgreen;}"
  639. else:
  640. style += self.defaultButtonStyleSheet
  641. style += "QPushButton:focus{background-color: lightgrey!important; border-color: lightblue;}" # Note: this does not work
  642. button.setStyleSheet(style)
  643. def plot(self, type=None):
  644. """
  645. Wrapper function to call the correct plot function depending on type
  646. :param type: (int) the plot type
  647. :return: -
  648. """
  649. try:
  650. if type == None:
  651. if self.theType in [ERROR, NO_DATA]:
  652. type = self._old_type
  653. else:
  654. type = self.theType
  655. self.do_plot(type)
  656. except Exception:
  657. if self.data is None or len(self.data.array) == 0:
  658. self.changePlotType(NO_DATA)
  659. else:
  660. self.changePlotType(ERROR)
  661. self.plot_widget.plot(None)
  662. def plot_live(self, type=None, data=None):
  663. """
  664. Function to call when livedata is to be plotted
  665. :param type: (int) plot type
  666. :param data: (dataset.DataSet) data to plot
  667. :return: -
  668. """
  669. self.data = data
  670. try:
  671. if type == None:
  672. if self.theType in [ERROR, NO_DATA]:
  673. type = self._old_type
  674. else:
  675. type = self.theType
  676. if type == PlotType.Trains or type == PlotType.Combined:
  677. self.do_plot(type, autorange=True)
  678. else:
  679. self.do_plot(type, autorange=False)
  680. except Exception:
  681. if len(self.data.array) == 0:
  682. self.changePlotType(NO_DATA)
  683. else:
  684. self.changePlotType(ERROR)
  685. self.plot_widget.plot(None)
  686. def do_plot(self, type, autorange=True):
  687. """
  688. Actually perform a plot (this calls SubPlotWidget.plot)
  689. :param type: (int) plot type
  690. :param autorange: (bool) whether to perform a autorange upon plot
  691. :return: -
  692. """
  693. if self.theType != type:
  694. if type == PlotType.Combined:
  695. self.groupWidget.hide()
  696. elif self.theType == PlotType.Combined:
  697. self.groupWidget.show()
  698. if type == PlotType.Compare:
  699. self.groupWidget.hide()
  700. self.groupWidgetCompare.show()
  701. self.compare_heading.show()
  702. else:
  703. self.groupWidget.show()
  704. self.groupWidgetCompare.hide()
  705. self.compare_heading.hide()
  706. self.changePlotType(type)
  707. self.setWindowTitle(self.theName + " - " + plotList[type] + " - " + str(self.thePrefix))
  708. self.change_identifier_text()
  709. if self.theType == PlotType.Compare:
  710. self.change_identifier_text()
  711. if self.theType == PlotType.Compare:
  712. self.groupWidget.hide()
  713. self.groupWidgetCompare.show()
  714. self.compare_heading.show()
  715. else:
  716. self.groupWidgetCompare.hide()
  717. self.compare_heading.hide()
  718. f = self.from_spinbox.value()
  719. t = self.to_spinbox.value() if self.to_spinbox.value() > f else f+1
  720. if type == PlotType.FFT:
  721. self.plot_widget.plot(np.abs(self.data.fft(adc=self.adc, frm=f, to=t+1, drop_first_bin=True)).transpose(),
  722. autorange=autorange,
  723. xvalueborders=[self.data.fft_freq_dist(), self.data.fft_max_freq()])
  724. if type == PlotType.Heatmap:
  725. self.plot_widget.plot(self.data.heatmap(adc=self.adc, frm=f, to=t).transpose(), autorange=autorange)
  726. if type == PlotType.Compare:
  727. 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)
  728. if type == PlotType.Trains:
  729. self.plot_widget.plot(self.data.train(adc=self.adc, frm=f, to=t), autorange=autorange)
  730. if type == PlotType.Combined:
  731. self.plot_widget.plot(self.data.combined(frm=f, to=t), autorange=autorange)
  732. def change_identifier_text(self):
  733. if self.theType is PlotType.Compare:
  734. the_text = str(self.adc)+"+"+str(self.secadc)
  735. else:
  736. the_text = str(self.adc)
  737. if self.theType in [ERROR, NO_DATA]:
  738. type = self._old_type
  739. else:
  740. type = self.theType
  741. if self.theDataType is LIVE:
  742. self.change_type_signal.emit(self.theId, type,
  743. the_text+" B: "+available_boards.get_board_name_from_id(self.board_id))
  744. else:
  745. self.change_type_signal.emit(self.theId, type, the_text)
  746. def closeEvent(self, event):
  747. """
  748. Event Handler to handle the event of closing this window
  749. :param event: QEvent
  750. :return: -
  751. """
  752. if not self.close_silent:
  753. if not self.parent.remove_plot(self.theId, silent=self.close_silent):
  754. event.ignore()
  755. return
  756. del self.data
  757. del self.plot_widget
  758. super(PlotWidget, self).closeEvent(event)
  759. if self.theDataType == LIVE:
  760. live_plot_windows.removeWindow(self.board_id, self)
  761. self.close_signal.emit()
  762. del self