PlotWidget.py 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228
  1. """
  2. This Module implements the Plot Windows used in KCG
  3. """
  4. import pyqtgraph as pg
  5. try: # on some versions of pyqtgraph useOpenGl is no valid option
  6. pg.setConfigOption('useOpenGl', True)
  7. except:
  8. print('op GL not valid')
  9. pass
  10. pg.setConfigOption('background', 'w')
  11. pg.setConfigOption('foreground', 'k')
  12. import numpy as np
  13. from PyQt4 import QtCore, QtGui
  14. from PyQt4.QtGui import QColor
  15. from scipy.optimize import curve_fit
  16. import time
  17. import traceback
  18. from ..base.groupedelements import live_plot_windows
  19. from ..base.backend.DataSet import DataSet
  20. from ..base.backend import board
  21. from ..base.backend.board import available_boards
  22. from ..config import colours, coloursTrans
  23. from ..base import kcgwidget as kcgw
  24. from .. import config
  25. tr = kcgw.tr
  26. LIVE = 1
  27. FILE = 2
  28. ERROR = 99
  29. NO_DATA = 98
  30. class Enum():
  31. """
  32. Simple Enum Class (as this is not supported by clean python)
  33. """
  34. def __init__(self, *args):
  35. self.idx = 0
  36. for i in args:
  37. setattr(self, i, self.idx)
  38. self.idx += 1
  39. plotList = [tr("Label", "Heatmap"), tr("Label", "FFT"), tr("Label", "Trains"), tr("Label", "Combined"), tr("Label", "Compare"), "Peak"]
  40. PlotType = Enum(*[str(i) for i in plotList])
  41. # gradient = {'mode': 'rgb',
  42. # 'ticks': [(0.0, (0.37281, 0.11883, 3.53583, 255)),
  43. # (0.0390625, (4.9401150000000005, 3.858915, 22.635585, 255)),
  44. # (0.078125, (15.6417, 9.330449999999999, 45.29871, 255)),
  45. # (0.1171875, (29.74728, 12.13137, 69.44185499999999, 255)),
  46. # (0.15625, (46.774395000000005, 10.283895, 90.51760499999999, 255)),
  47. # (0.1953125, (64.1631, 9.614775, 102.86139, 255)),
  48. # (0.234375, (80.65191, 13.63995, 108.40458, 255)),
  49. # (0.2734375, (96.64525499999999, 19.444515, 110.343345, 255)),
  50. # (0.3125, (112.507785, 25.33119, 110.05646999999999, 255)),
  51. # (0.3515625, (128.390715, 31.001625, 107.95578, 255)),
  52. # (0.390625, (144.29277, 36.609585, 104.10579, 255)),
  53. # (0.4296875, (160.100985, 42.476625, 98.50038, 255)),
  54. # (0.46875, (175.606515, 49.020945, 91.188765, 255)),
  55. # (0.5078125, (190.517385, 56.70639, 82.32827999999999, 255)),
  56. # (0.546875, (204.477105, 65.96187, 72.190245, 255)),
  57. # (0.5859375, (217.10292, 77.07629999999999, 61.10718, 255)),
  58. # (0.625, (228.047775, 90.11674500000001, 49.36392, 255)),
  59. # (0.6640625, (237.05922, 104.927145, 37.068585, 255)),
  60. # (0.703125, (243.99726, 121.21578, 24.147225, 255)),
  61. # (0.7421875, (248.797635, 138.66849, 11.122589999999999, 255)),
  62. # (0.78125, (251.41776000000002, 157.01625, 6.52596, 255)),
  63. # (0.8203125, (251.80332, 176.04333, 20.397450000000003, 255)),
  64. # (0.859375, (249.90816, 195.543435, 42.420015, 255)),
  65. # (0.8984375, (245.92047, 215.18124, 69.714705, 255)),
  66. # (0.9375, (241.63647, 233.936745, 104.719575, 255)),
  67. # (0.9765625, (245.26206, 248.86062, 145.840875, 255)),
  68. # (0.99609375, (252.03231, 254.58282, 164.45562, 255))]}
  69. gradient = {'mode': 'rgb',
  70. 'ticks': [(0.0, (0.37281, 0.11883, 3.53583, 255)),
  71. (0.1171875, (29.74728, 12.13137, 69.44185499999999, 255)),
  72. (0.234375, (80.65191, 13.63995, 108.40458, 255)),
  73. (0.3515625, (128.390715, 31.001625, 107.95578, 255)),
  74. (0.46875, (175.606515, 49.020945, 91.188765, 255)),
  75. (0.5859375, (217.10292, 77.07629999999999, 61.10718, 255)),
  76. (0.703125, (243.99726, 121.21578, 24.147225, 255)),
  77. (0.8203125, (251.80332, 176.04333, 20.397450000000003, 255)),
  78. (0.9375, (241.63647, 233.936745, 104.719575, 255)),
  79. (0.99609375, (252.03231, 254.58282, 164.45562, 255))]}
  80. class CustomGradientEditorItem(pg.GradientEditorItem):
  81. """
  82. A Gradient Editor Item to insert a perception linear gradient
  83. """
  84. def __init__(self, **kwargs):
  85. pg.GradientEditorItem.__init__(self, **kwargs)
  86. self.customGradients = {}
  87. def addGrad(self, name, grad_dic):
  88. """
  89. Add a gradient to the list of gradients in the gui
  90. :param name: the name of the gradient
  91. :param grad_dic: the dictionary containing the gradient data
  92. """
  93. if name not in self.customGradients:
  94. self.customGradients[name] = grad_dic
  95. px = QtGui.QPixmap(100, 15)
  96. p = QtGui.QPainter(px)
  97. self.restoreState(grad_dic)
  98. length_backup = self.length
  99. self.length = 100
  100. grad = self.getGradient()
  101. self.length = length_backup
  102. brush = QtGui.QBrush(grad)
  103. p.fillRect(QtCore.QRect(0, 0, 100, 15), brush)
  104. p.end()
  105. label = QtGui.QLabel()
  106. label.setPixmap(px)
  107. label.setContentsMargins(1, 1, 1, 1)
  108. act = QtGui.QWidgetAction(self)
  109. act.setDefaultWidget(label)
  110. act.triggered.connect(self.contextMenuClicked)
  111. act.name = name
  112. if len(self.customGradients) > 1:
  113. self.menu.insertAction(self.menu.actions()[0], act)
  114. else:
  115. sep = self.menu.insertSeparator(self.menu.actions()[0])
  116. self.menu.insertAction(sep, act)
  117. self.restoreState(grad_dic)
  118. def restoreState(self, state):
  119. """
  120. Reimplemented of pyqtgraph.GradientEditorItem.restoreState to work with our custom perception linear gradient.
  121. :param state: the state to restore to
  122. """
  123. self.setColorMode(state['mode'])
  124. for t in list(self.ticks.keys()):
  125. self.removeTick(t, finish=False)
  126. self.bottomTick = [None, 10]
  127. self.topmostTick = [None, -1]
  128. for t in state['ticks']:
  129. c = QtGui.QColor(*t[1])
  130. tick = self.addTick(t[0], c, finish=False)
  131. if t[0] < self.bottomTick[1]:
  132. self.bottomTick = [tick, t[0]]
  133. if t[0] > self.topmostTick[1]:
  134. self.topmostTick = [tick, t[0]]
  135. self.tickPosInPercent = self.ticks.copy()
  136. for t in self.ticks:
  137. if t is self.topmostTick[0] or t is self.bottomTick[0]:
  138. continue
  139. t.hide()
  140. t.movable = False
  141. # self.bottomTick[1] *= self.length
  142. # self.topmostTick[1] *= self.length
  143. self.updateGradient()
  144. self.sigGradientChangeFinished.emit(self)
  145. def loadPreset(self, name):
  146. """
  147. Reimplemented of pyqtgraph.GradientEditorItem.loadPreset to work with our custom perception linear gradient.
  148. :param name: the name of the preset to load
  149. """
  150. if name in self.customGradients:
  151. self.restoreState(self.customGradients[name])
  152. else:
  153. super(CustomGradientEditorItem, self).loadPreset(name)
  154. def tickMoved(self, tick, pos):
  155. """
  156. Reimplemented of pyqtgraph.GradientEditorItem.tickMoved to work with our custom perception linear gradient,
  157. which has a lot of steps which would create a lot of ticks in the gradient legend. This removes all but 2
  158. ticks and aligns all the internal ticks accordingly.
  159. :param tick: the tick to move
  160. :param pos: the position to move to
  161. """
  162. if tick is self.bottomTick[0]:
  163. # self.bottomTick[1] = pos.x()
  164. pg.TickSliderItem.tickMoved(self, tick, pos)
  165. elif tick is self.topmostTick[0]:
  166. # self.topmostTick[1] = pos.x()
  167. pg.TickSliderItem.tickMoved(self, tick, pos)
  168. if tick in [self.bottomTick[0], self.topmostTick[0]]: # if bottom or topmost tick was moved
  169. if pos.x() < self.length and pos.x() > 0: # if tick is not at top or bottom
  170. newUnit = (self.topmostTick[0].pos().x() - self.bottomTick[0].pos().x()) # create new virtual unit length
  171. for t in self.ticks: # for every tick
  172. if t not in [self.bottomTick[0], self.topmostTick[0]]: # if tick is not bottom or topmost tick
  173. pos = t.pos()
  174. new_x = self.bottomTick[0].pos().x() + self.tickPosInPercent[t] * newUnit
  175. pos.setX(new_x)
  176. t.setPos(pos)
  177. pg.TickSliderItem.tickMoved(self, t, pos)
  178. self.updateGradient()
  179. # .d8888b. 888 .d8888b. 888
  180. # d88P Y88b 888 d88P Y88b 888
  181. # Y88b. 888 888 888 888
  182. # "Y888b. 88888b. .d88b. .d8888b888888888d888 .d88b. .d88b. 888d888 8888b. 88888b.d88b. 888 .d88b. 888 .d88b. 888d888
  183. # "Y88b.888 "88bd8P Y8bd88P" 888 888P" d88""88bd88P"88b888P" "88b888 "888 "88b888 d88""88b888d88""88b888P"
  184. # "888888 88888888888888 888 888 888 888888 888888 .d888888888 888 888888 888888 888888888 888888
  185. # Y88b d88P888 d88PY8b. Y88b. Y88b. 888 Y88..88PY88b 888888 888 888888 888 888Y88b d88PY88..88P888Y88..88P888
  186. # "Y8888P" 88888P" "Y8888 "Y8888P "Y888888 "Y88P" "Y88888888 "Y888888888 888 888 "Y8888P" "Y88P" 888 "Y88P" 888
  187. # 888 888
  188. # 888 Y8b d88P
  189. # 888 "Y88P"
  190. #
  191. class SpectrogramColorLegendItem(pg.GraphicsWidget):
  192. """
  193. The Item used as Legend for Heatmap and FFT Plot
  194. """
  195. gradientChanged = QtCore.pyqtSignal()
  196. def __init__(self, img=None):
  197. """
  198. Initialise the Legend
  199. :param img: (ImageItem) the image item this is the legend for
  200. :return: -
  201. """
  202. super(SpectrogramColorLegendItem, self).__init__()
  203. self.layout = QtGui.QGraphicsGridLayout()
  204. self.setLayout(self.layout)
  205. self.img = img
  206. # self.gei = pg.GradientEditorItem(orientation='right')
  207. self.gei = CustomGradientEditorItem(orientation='right', allowAdd=False)
  208. self.legend_axis = pg.AxisItem(orientation='left')
  209. # self.addItem(self.legend_axis)
  210. self.layout.addItem(self.legend_axis, 0, 0, alignment=QtCore.Qt.AlignVCenter)
  211. # self.addItem(self.gei)
  212. self.layout.addItem(self.gei, 0, 1)
  213. self.gei.sigGradientChanged.connect(self.gradient_changed)
  214. # self.gei.loadPreset('spectrum')
  215. self.gei.addGrad('inferno', gradient)
  216. # self.setFixedWidth(80)
  217. self.image_changed()
  218. def gradient_changed(self):
  219. """
  220. Proxy function for the gradient_cnahged event of the GradientEditorItem
  221. :return:
  222. """
  223. self.gradientChanged.emit()
  224. def set_image(self, img):
  225. """
  226. Set the Image this is the legend for (only needed if not already done upon initialisation)
  227. :param img: (ImageItem) The image item to use
  228. :return: -
  229. """
  230. self.img = img
  231. self.image_changed(True)
  232. def update_axis(self):
  233. """
  234. Update the axis of this legend
  235. :return: -
  236. """
  237. if self.img.getLevels() is not None:
  238. # min, max = self.img.getLevels()
  239. min, max = (np.min(self.img.image), np.max(self.img.image))
  240. self.legend_axis.setRange(min, max)
  241. def reset_gradient(self):
  242. """
  243. Reset the gradient to the preset "spectrum"
  244. :return: -
  245. """
  246. # self.gei.loadPreset('spectrum')
  247. self.gei.loadPreset('inferno')
  248. # self.gei.restoreState(gradient)
  249. def image_changed(self, reset_gradient=True):
  250. """
  251. Call this to adjust the legend when the image has changed
  252. :param reset_gradient: (bool) whether to reset the gradient or not
  253. :return:
  254. """
  255. if self.img:
  256. self.update_axis()
  257. if reset_gradient:
  258. self.reset_gradient()
  259. def getLookupTable(self, nPts, alpha=None):
  260. """
  261. Get the look up table of the imageitem
  262. :param nPts: (int) number of points the lookup table is defined on
  263. :param alpha: ??
  264. :return: -
  265. """
  266. return self.gei.getLookupTable(nPts, alpha=alpha)
  267. def resizeEvent(self, event):
  268. """
  269. Handle resizing of the window
  270. """
  271. self.legend_axis.setHeight(self.gei.length)
  272. # .d8888b. 888 8888888b. 888 888 888 888d8b 888 888
  273. # d88P Y88b 888 888 Y88b888 888 888 o 888Y8P 888 888
  274. # Y88b. 888 888 888888 888 888 d8b 888 888 888
  275. # "Y888b. 888 88888888b. 888 d88P888 .d88b. 888888888 d888b 888888 .d88888 .d88b. .d88b. 888888
  276. # "Y88b.888 888888 "88b8888888P" 888d88""88b888 888d88888b888888d88" 888d88P"88bd8P Y8b888
  277. # "888888 888888 888888 888888 888888 88888P Y88888888888 888888 88888888888888
  278. # Y88b d88PY88b 888888 d88P888 888Y88..88PY88b. 8888P Y8888888Y88b 888Y88b 888Y8b. Y88b.
  279. # "Y8888P" "Y8888888888P" 888 888 "Y88P" "Y888888P Y888888 "Y88888 "Y88888 "Y8888 "Y888
  280. # 888
  281. # Y8b d88P
  282. # "Y88P"
  283. #
  284. class SubPlotWidget(pg.GraphicsLayoutWidget):
  285. """
  286. The Widget actually containing the plots and images
  287. """
  288. def __init__(self, dType=FILE):
  289. super(SubPlotWidget, self).__init__()
  290. self.dType = dType
  291. self.adc_number = board.get_board_config(available_boards[0]).get('adc_number')
  292. self._type_changed = False
  293. # self.setStyleSheet("border: 5px solid black; margin: 10px;")
  294. self.plotType = 1
  295. self.fft_mode = 0
  296. self.plotItem = pg.PlotItem()
  297. self.img = pg.ImageItem()
  298. self.plotItem2 = pg.PlotItem()
  299. self.img2 = pg.ImageItem()
  300. self.plotItem2.addItem(self.img2)
  301. self.setFrameStyle(self.StyledPanel | self.Sunken)
  302. self.gradient_legend = SpectrogramColorLegendItem(self.img)
  303. def changelut():
  304. """
  305. Handle changing of the lookup table
  306. """
  307. self.img.setLookupTable(self.gradient_legend.getLookupTable(512))
  308. self.img2.setLookupTable(self.gradient_legend.getLookupTable(512))
  309. self.gradient_legend.gradientChanged.connect(changelut)
  310. self.addItem(self.plotItem)
  311. self.plotItem.addItem(self.img)
  312. self.plotItemPlotScatter = pg.PlotDataItem(pen=None, symbolPen=pg.mkPen(50, 50, 255), symbolSize=3, pxMode=True, symbolBrush=pg.mkBrush(50, 50, 255), downsample=200)
  313. self.plotItemPlotScatter1 = pg.PlotDataItem(pen=None, symbolPen=pg.mkPen(255, 50, 50), symbolSize=3, pxMode=True, symbolBrush=pg.mkBrush(255, 50, 50), downsample=200)
  314. self.plotItemFit = []
  315. # self.plotItemPlotScatter2 = pg.PlotDataItem(pen=None, symbolPen=pg.mkPen(50, 255, 50), symbolSize=3, pxMode=True, symbolBrush=pg.mkBrush(50, 255, 50), downsample=200)
  316. self.plotItemPlot = [] #pg.PlotDataItem() # pen="#000000")
  317. #col = [QColor(44, 62, 80), QColor(39, 174, 96), QColor(143, 156, 18), QColor(192, 57, 43), QColor(41, 128, 185), QColor(142, 68, 173), QColor(22, 160, 133,150), QColor(127, 140, 141)]
  318. style = [QtCore.Qt.SolidLine, QtCore.Qt.DashLine, QtCore.Qt.DotLine, QtCore.Qt.DashDotLine, QtCore.Qt.DashLine, QtCore.Qt.DotLine, QtCore.Qt.DashDotLine, QtCore.Qt.DashLine]
  319. for i in range(self.adc_number):
  320. self.plotItemPlot.append(pg.PlotDataItem(pen=pg.mkPen(colours[i], width=0.5, style=style[i]), autoDownsample=True, clipToView=True ) )#, symbolPen=pg.mkPen(col[i], width=1, style=style[i]), symbolSize=2))##, pxMode=True))
  321. self.plotItem.addItem(self.plotItemPlot[i])
  322. self.plotItem.addItem(self.plotItemPlotScatter)
  323. self.plotItem.addItem(self.plotItemPlotScatter1)
  324. # self.plotItem.addItem(self.plotItemPlotScatter2)
  325. self.plotItem.vb.origAutoRange = self.plotItem.vb.autoRange
  326. self.error_label = pg.LabelItem("Error during plot.", color=(200, 50, 50))
  327. self.error_label.hide()
  328. self.error_label.scale(1, -0.6)
  329. self.error_label.translate(0, -30)
  330. self.plotItem.addItem(self.error_label)
  331. self.no_data_label = pg.LabelItem("No data to plot.", color=(200, 50, 50))
  332. self.no_data_label.hide()
  333. self.no_data_label.scale(1, -0.6)
  334. self.no_data_label.translate(0, -30)
  335. self.plotItem.addItem(self.no_data_label)
  336. def _enableCustomAutoRange(self, data, lendata=0):
  337. """
  338. Enable custom auto range in this plot
  339. :param data: the data to autorange
  340. :return:
  341. """
  342. def newAutoRange(*args, **kwargs):
  343. ''' function to handle the new autorange '''
  344. xmax = lendata
  345. if lendata == 0:
  346. if len(data) == 0:
  347. return
  348. xmax = len(data)
  349. if self.plotType == PlotType.Combined:
  350. xmax /= self.adc_number
  351. if self.plotType == PlotType.Peak:
  352. xmax = 184
  353. bounds = [np.min(data), np.max(data)]
  354. self.plotItem.vb.setRange(xRange=[0, xmax],
  355. yRange=[bounds[0]-0.1*(bounds[1]-bounds[0])-1, bounds[1]+0.1*(bounds[1]-bounds[0])+1], update=True)
  356. self.plotItem.update()
  357. self.plotItem.vb.autoRange = newAutoRange
  358. self.plotItem.autoBtn.clicked.disconnect()
  359. self.plotItem.autoBtn.clicked.connect(newAutoRange)
  360. def _disableCustomAutoRange(self):
  361. """
  362. Disable the custom autorange and reset it to default
  363. :return: -
  364. """
  365. self.plotItem.vb.autoRange = self.plotItem.vb.origAutoRange
  366. self.plotItem.autoBtn.clicked.disconnect()
  367. self.plotItem.autoBtn.clicked.connect(self.plotItem.autoBtnClicked)
  368. def plotPeak(self, data):
  369. """
  370. Plot the datapoints of all available ADCs as time-series data and
  371. optionally do a polyfit between the points to visualize an
  372. approximation of the shape of the pulse that was measured.
  373. :param data: The 'raw' data to be plotted
  374. :return: -
  375. """
  376. self.img.clear()
  377. self.plotItemPlotScatter.show()
  378. self.plotItemPlotScatter1.show()
  379. baseline = []
  380. #Currently not supported / cleanup required
  381. #doFit = 6 if self.fittGauss.isChecked() else 0
  382. doFit = 0
  383. if doFit > 0:
  384. x = np.reshape(data[0][0], (-1, doFit))
  385. y = np.reshape(data[0][1], (-1, doFit))
  386. valmax = np.max(y)
  387. valmin = np.min(y)
  388. positive = np.abs(valmax) > np.abs(valmin)
  389. #print('positive', positive)
  390. tmp = y.T
  391. for line in tmp:
  392. if positive:
  393. baseline.append(np.mean(line[np.where(line < 15)]))
  394. else:
  395. baseline.append(np.mean(line[np.where(line > -15)]))
  396. #print(baseline)
  397. y = y-baseline
  398. def gauss(x, sigma, mu, A):
  399. return A*np.exp(-0.5*((x-mu)/sigma)**2)
  400. fitx = np.linspace(np.min(x[0])*0.5, np.max(x[0])*1.5, 25)
  401. for i, line in enumerate(x):
  402. if len(self.plotItemFit) < i+1:
  403. item = pg.PlotDataItem(pen=pg.mkPen(QColor(192, 57, 43), width=0.5), autoDownsample=True, clipToView=True )
  404. self.plotItemFit.append(item)
  405. self.plotItem.addItem(item)
  406. if (np.mean(y[i]) < 15 and positive) or (np.mean(y[i]) > -15 and not positive):
  407. #if np.mean(y[i]) > 15*(1 if positive else -1):
  408. #print('skip ', i)
  409. continue
  410. try:
  411. #print(i)
  412. popt, pcov = curve_fit(gauss, x[i]-i%184, y[i])
  413. self.plotItemFit[i].setData(fitx+i%184, gauss(fitx, *popt))
  414. except:
  415. traceback.print_exc()
  416. pass
  417. if doFit:
  418. self.plotItemPlotScatter.setData(np.reshape(x,-1), np.reshape(y,-1))
  419. else:
  420. self.plotItemPlotScatter.setData(data[0].transpose())
  421. self.plotItemPlotScatter1.setData(data[1].transpose())
  422. self._enableCustomAutoRange(data[0][1])
  423. self.plotItem.setClipToView(False)
  424. #13.10.2020 by Timo:
  425. #The plot function has become a spaghetti nightmare. Meatballs included...
  426. #It's time to begin cleaning it up!
  427. #Please don't add additional functionality to the plot function call.
  428. #The function is hereby officially deprecated!
  429. #
  430. #Guidelines:
  431. #- Have only one dedicated method for each type of plot
  432. #- Keep the amounts of function paramters to a minimum.
  433. #- If you absolutely NEED a lot of parameters, use a Dict
  434. #- Do thorough parameter checking as early as possible
  435. #- If you HAVE to use a Try/Catch block, DON'T JUST SILENTLY "pass" THE EXCEPTION!!!
  436. #- Read "The Zen of Python" and try to stick to it
  437. #- KISS principle, please
  438. @QtCore.pyqtSlot(np.ndarray, tuple, tuple)
  439. def plot(self, data, xvalueborders=None, yvalueborders=None, autorange=True, fft_mode=0, log=False, doFit=0):
  440. """
  441. Plot Data. The plot type depends on the type property
  442. :param data: (dataset.DataSet) data to plot
  443. :param xvalueborders: (touple) the borders for the xvalues
  444. :param yvalueborders: (touple) the borders for the yvalues
  445. :param autorange: (bool) whether to perform a autorange or not
  446. :return: -
  447. """
  448. self.error_label.hide()
  449. self.no_data_label.hide()
  450. for item in self.plotItemPlot:
  451. item.clear()
  452. item.hide()
  453. self.plotItemPlotScatter.clear()
  454. self.plotItemPlotScatter1.clear()
  455. for item in self.plotItemFit:
  456. item.clear()
  457. #self.plotItemPlotScatter2.clear()
  458. #self.plotItem.resetTransform()
  459. #self.plotItem.getAxis('bottom').setScale()
  460. self.img.clear()
  461. self.img2.clear()
  462. self.img.hide()
  463. self.img2.hide()
  464. self.gradient_legend.hide()
  465. try:
  466. self.removeItem(self.gradient_legend)
  467. pass
  468. except Exception as e:
  469. if not "Could not determine index of item" in str(e):
  470. raise
  471. if self._type_changed:
  472. #self.plotItem.autoBtnClicked()
  473. pass
  474. try:
  475. self.removeItem(self.plotItem2)
  476. except Exception as e:
  477. if "Could not determine index of item" in str(e):
  478. pass
  479. else:
  480. raise
  481. if self.plotType == PlotType.FFT:
  482. self.fft_mode = fft_mode
  483. if fft_mode:
  484. if len(data) > 9:
  485. data = [data]
  486. maxv = 0
  487. minv = 400000
  488. for i in range(len(data)):
  489. if len(data[i]) < 10:
  490. continue
  491. self.plotItemPlot[i].show()
  492. dat = np.abs(data[i]).transpose()
  493. if fft_mode == 1:
  494. dat = np.abs(data[i])
  495. elif fft_mode == 2:
  496. dat = np.mean(dat,1)
  497. else:
  498. dat = dat[:,fft_mode-2]
  499. if log:
  500. dat=np.log(dat)
  501. self.plotItemPlot[i].setData(dat)
  502. v = np.max(dat)
  503. if maxv < v:
  504. maxv = v
  505. v = np.min(dat)
  506. if minv > v:
  507. minv = v
  508. """
  509. if fft_mode == 1:
  510. dat = np.mean(data,1)
  511. else:
  512. dat = data[:,fft_mode-2]
  513. self.plotItemPlot[0].show()
  514. self.plotItemPlot[0].setData(dat)
  515. """
  516. if xvalueborders:
  517. self.plotItem.getAxis('bottom').setScale((xvalueborders[1]-xvalueborders[0])/float(dat.shape[0]))
  518. self._enableCustomAutoRange([maxv, minv], lendata=len(dat))
  519. else:
  520. if len(data) < 9:
  521. data= data[0]
  522. data = np.abs(data).transpose()
  523. self.addItem(self.gradient_legend)
  524. self.gradient_legend.show()
  525. self.img.show()
  526. self.img.setImage(data, scale=[1, 1/1000.])
  527. self.img.resetTransform()
  528. self.img.setAutoDownsample(True)
  529. if autorange:
  530. self.gradient_legend.image_changed()
  531. if xvalueborders:
  532. self.plotItem.getAxis('bottom').setScale((xvalueborders[1]-xvalueborders[0])/float(data.shape[0]))
  533. self.img.translate(xvalueborders[0]/self.plotItem.getAxis('bottom').scale, 0)
  534. else:
  535. self.plotItem.getAxis('bottom').setScale()
  536. if yvalueborders:
  537. self.img.translate(0, yvalueborders[0])
  538. self.plotItem.getAxis('left').setScale(yvalueborders[1]/float(data.shape[1]))
  539. self._disableCustomAutoRange()
  540. self.plotItem.setClipToView(True)
  541. if self.plotType == PlotType.Heatmap:
  542. self.addItem(self.gradient_legend)
  543. self.gradient_legend.show()
  544. self.img.show()
  545. self.img.setImage(data, scale=[1, 1/1000.])
  546. self.img.resetTransform()
  547. self.img.setAutoDownsample(True)
  548. if autorange:
  549. self.gradient_legend.image_changed()
  550. if xvalueborders:
  551. self.plotItem.getAxis('bottom').setScale((xvalueborders[1]-xvalueborders[0])/float(data.shape[0]))
  552. self.img.translate(xvalueborders[0]/self.plotItem.getAxis('bottom').scale, 0)
  553. else:
  554. self.plotItem.getAxis('bottom').setScale()
  555. if yvalueborders:
  556. self.img.translate(0, yvalueborders[0])
  557. self.plotItem.getAxis('left').setScale(yvalueborders[1]/float(data.shape[1]))
  558. self._disableCustomAutoRange()
  559. self.plotItem.setClipToView(True)
  560. if self.plotType == PlotType.Compare:
  561. self.addItem(self.plotItem2)
  562. self.plotItem2.vb.linkView(self.plotItem2.vb.XAxis, self.plotItem.vb)
  563. self.plotItem2.vb.linkView(self.plotItem2.vb.YAxis, self.plotItem.vb)
  564. self.addItem(self.gradient_legend)
  565. self.gradient_legend.show()
  566. self.img.setImage(data[0], scale=[1, 1/1000.])
  567. self.img.show()
  568. self.img.resetTransform()
  569. self.img2.setImage(data[1], scale=[1, 1/1000.])
  570. self.img2.show()
  571. self.img2.resetTransform()
  572. self.img.setAutoDownsample(True)
  573. self.img2.setAutoDownsample(True)
  574. if autorange:
  575. self.gradient_legend.image_changed()
  576. pass
  577. if xvalueborders:
  578. self.plotItem.getAxis('bottom').setScale((xvalueborders[1]-xvalueborders[0])/float(data[0].shape[0]))
  579. self.plotItem2.getAxis('bottom').setScale((xvalueborders[1]-xvalueborders[0])/float(data[1].shape[0]))
  580. self.img.translate(xvalueborders[0]/self.plotItem.getAxis('bottom').scale, 0)
  581. self.img2.translate(xvalueborders[0]/self.plotItem.getAxis('bottom').scale, 0)
  582. else:
  583. self.plotItem.getAxis('bottom').setScale()
  584. self.plotItem2.getAxis('bottom').setScale()
  585. if yvalueborders:
  586. self.img.translate(0, yvalueborders[0])
  587. self.img2.translate(0, yvalueborders[0])
  588. self.plotItem.getAxis('left').setScale(yvalueborders[1]/float(data[0].shape[1]))
  589. self.plotItem2.getAxis('left').setScale(yvalueborders[1]/float(data[1].shape[1]))
  590. self._disableCustomAutoRange()
  591. self.plotItem.setClipToView(True)
  592. if self.plotType == PlotType.Trains:
  593. if len(data) < 9:
  594. maxv = 0
  595. minv = 4000
  596. maxl = 0
  597. for i in range(len(data)):
  598. if np.mean(data[i]) == 0:
  599. continue
  600. self.plotItemPlot[i].show()
  601. self.plotItemPlot[i].setData(data[i])
  602. l = len(data[i])
  603. if maxl < l:
  604. maxl = l
  605. v = np.max(data[i])
  606. if maxv < v:
  607. maxv = v
  608. v = np.min(data[i])
  609. if minv > v:
  610. minv = v
  611. self._enableCustomAutoRange([maxv, minv], lendata=maxl)
  612. else:
  613. self.plotItemPlot[0].show()
  614. self.plotItemPlot[0].setData(data)
  615. self._enableCustomAutoRange(data)
  616. if self.plotType in [PlotType.Combined]:
  617. self.img.clear()
  618. self.plotItemPlotScatter.show()
  619. self.plotItemPlotScatter1.show()
  620. self.plotItemPlotScatter.setData(data[0].transpose())
  621. self.plotItemPlotScatter1.setData(data[1].transpose())
  622. self._enableCustomAutoRange(data[0][1])
  623. self.plotItem.setClipToView(False) # NOTE: otherwise only a very small portion of data is visible :wonder:
  624. if self.plotType == PlotType.Peak:
  625. self.plotPeak(data)
  626. if self.plotType == ERROR or self.plotType == NO_DATA:
  627. self.img.hide()
  628. self.gradient_legend.hide()
  629. self.img2.hide()
  630. #self.plotItemPlot.hide()
  631. self.plotItemPlotScatter.hide()
  632. self.plotItemPlotScatter1.hide()
  633. if self.plotType == ERROR:
  634. self.error_label.show()
  635. else:
  636. self.no_data_label.show()
  637. if autorange:
  638. self.plotItem.getViewBox().autoRange()
  639. self.labelAxes()
  640. if self.plotType in [PlotType.Heatmap, PlotType.Compare]:
  641. self.gradient_legend.update_axis()
  642. if self.plotType == PlotType.FFT and self.fft_mode == 0:
  643. self.gradient_legend.update_axis()
  644. def labelAxes(self):
  645. """
  646. Add Labels to the axis depending on the self.plotType property.
  647. :return: -
  648. """
  649. if self.plotType == PlotType.Heatmap:
  650. self.plotItem.setLabel('bottom', 'Turn', '')
  651. self.plotItem.setLabel('left', 'Bunch Position', '')
  652. elif self.plotType == PlotType.FFT:
  653. self.plotItem.setLabel('left', 'Bunch Position', '')
  654. self.plotItem.setLabel('bottom', 'Frequency', 'Hz')
  655. elif self.plotType == PlotType.Trains:
  656. self.plotItem.setLabel('left', '', '')
  657. self.plotItem.setLabel('bottom', 'Sample Point', '')
  658. elif self.plotType == PlotType.Combined:
  659. self.plotItem.setLabel('left', '', '')
  660. self.plotItem.setLabel('bottom', '', '')
  661. if self.plotType == PlotType.Compare:
  662. self.plotItem.setLabel('bottom', 'Turn', '')
  663. self.plotItem.setLabel('left', 'Bunch Position', '')
  664. self.plotItem2.setLabel('bottom', 'Turn', '')
  665. self.plotItem2.setLabel('left', 'Bunch Position', '')
  666. def changeType(self, type):
  667. """
  668. Change the plot Type
  669. :param type: (int) the new type
  670. :return: -
  671. """
  672. if type != self.plotType:
  673. self._type_changed = True
  674. self.plotType = type
  675. # if type in [PlotType.FFT, PlotType.Compare, PlotType.Heatmap]: # this will generate an error
  676. # self.gradient_legend.image_changed()
  677. # 8888888b. 888 888 888 888d8b 888 888
  678. # 888 Y88b888 888 888 o 888Y8P 888 888
  679. # 888 888888 888 888 d8b 888 888 888
  680. # 888 d88P888 .d88b. 888888888 d888b 888888 .d88888 .d88b. .d88b. 888888
  681. # 8888888P" 888d88""88b888 888d88888b888888d88" 888d88P"88bd8P Y8b888
  682. # 888 888888 888888 88888P Y88888888888 888888 88888888888888
  683. # 888 888Y88..88PY88b. 8888P Y8888888Y88b 888Y88b 888Y8b. Y88b.
  684. # 888 888 "Y88P" "Y888888P Y888888 "Y88888 "Y88888 "Y8888 "Y888
  685. # 888
  686. # Y8b d88P
  687. # "Y88P"
  688. #
  689. class PlotWidget(kcgw.KCGWidgets):
  690. """
  691. The container Class holding various buttons and controls and the actual plots as SubPlotWidgets instance
  692. """
  693. close_signal = QtCore.pyqtSignal()
  694. change_type_signal = QtCore.pyqtSignal(int, int, str)
  695. def __init__(self, board_id, parent=None, name=None, unique_id=None, type=None, datatype=None, prefix=None, fName=None, data=None):
  696. """
  697. Initialise the Plot Widgt
  698. :param parent: (QWidget) the parent widget
  699. :param name: (str) name of this widget
  700. :param unique_id: (int) unique id of this widget
  701. :param type: (int) type of this widget
  702. :param datatype: (int) datatype (LIVE or FILE)
  703. :param prefix: (str) prefix for text in the listview in LeftBar
  704. :param fName: (str) the filename (this is only used if datatype is FILE)
  705. :param data: (dataset.DataSet) the data to be plotted
  706. :return: -
  707. """
  708. super(PlotWidget, self).__init__()
  709. self.board_id = board_id
  710. self.board_config = board.get_board_config(available_boards[0])
  711. if name != None:
  712. self.theName = name
  713. if unique_id != None:
  714. self.theId = unique_id
  715. if type != None:
  716. self.theType = type
  717. self._old_type = type
  718. if datatype != None:
  719. self.theDataType = datatype
  720. if prefix != None:
  721. self.thePrefix = prefix
  722. if self.theDataType == FILE:
  723. self.fName = fName
  724. if self.theDataType == FILE:
  725. self.data = data
  726. else:
  727. self.data = None
  728. self.close_silent = False
  729. self.parent = parent
  730. self.adc = 1
  731. self.secadc = 2
  732. self._single_adc_checked = False
  733. self.initUI()
  734. self.changePlotType(self.theType) # initially mark the correct button
  735. self.setWindowTitle(self.theName + " - " + plotList[type] + " - " + str(self.thePrefix))
  736. if self.theDataType == FILE:
  737. self.plot(type)
  738. else:
  739. live_plot_windows.addWindow(board_id, self)
  740. if board.get_board_status(board_id).last_file is not None:
  741. self.data = DataSet(board.get_board_status(board_id).last_file, tRev=config.tRev, bunchesPerTurn=config.bunches_per_turn, shiftFMC2=config.shiftFMC2)
  742. self.plot(type)
  743. def initUI(self):
  744. """
  745. Initialise the UI
  746. :return: -
  747. """
  748. self.plot_widget = SubPlotWidget(dType=self.theDataType)
  749. self.layout = QtGui.QVBoxLayout()
  750. self.plot_buttons_layout = QtGui.QHBoxLayout()
  751. self.heatmap_button = self.createButton(text=tr("Button", "Heatmap"), connect=lambda: self.plot(type=PlotType.Heatmap))
  752. self.fft_button = self.createButton(text=tr("Button", "FFT"), connect=lambda: self.plot(type=PlotType.FFT))
  753. self.trains_button = self.createButton(text=tr("Button", "Trains"), connect=lambda: self.plot(type=PlotType.Trains))
  754. self.combined_button = self.createButton(text=tr("Button", "Combined"), connect=lambda: self.plot(type=PlotType.Combined))
  755. self.compare_button = self.createButton(text=tr("Button", "Compare"), connect=lambda: self.plot(type=PlotType.Compare))
  756. self.peak_button = self.createButton(text="Peak", connect=lambda: self.plot(type=PlotType.Peak))
  757. self.type_buttons = {PlotType.Heatmap:self.heatmap_button, PlotType.FFT:self.fft_button,
  758. PlotType.Trains:self.trains_button, PlotType.Combined:self.combined_button,
  759. PlotType.Compare:self.compare_button, PlotType.Peak:self.peak_button}
  760. self.defaultButtonStyleSheet = self.heatmap_button.styleSheet()
  761. self.plot_buttons_layout.addWidget(self.heatmap_button)
  762. self.plot_buttons_layout.addWidget(self.fft_button)
  763. self.plot_buttons_layout.addWidget(self.trains_button)
  764. self.plot_buttons_layout.addWidget(self.combined_button)
  765. self.plot_buttons_layout.addWidget(self.compare_button)
  766. self.plot_buttons_layout.addWidget(self.peak_button)
  767. self.adcCheckBox = []
  768. self.adc_checkbox_layout = QtGui.QHBoxLayout()
  769. self.group = QtGui.QButtonGroup()
  770. for i in range(int(self.board_config.get('adc_number'))):
  771. self.adcCheckBox.append(self.createCheckbox(text="ADC "+str(i+1), connect=self.change_adc, color=colours[i]))
  772. self.adc_checkbox_layout.addWidget(self.adcCheckBox[i])
  773. self.group.addButton(self.adcCheckBox[i], i+1)
  774. self.adcCheckBox[0].setChecked(True)
  775. self.groupWidget = QtGui.QWidget()
  776. self.groupWidget.setLayout(self.adc_checkbox_layout)
  777. self.group.setExclusive(True)
  778. self.compare_heading_left = self.createLabel("ADC 1")
  779. self.compare_heading_left.setAlignment(QtCore.Qt.AlignCenter)
  780. self.compare_heading_right = self.createLabel("ADC 2")
  781. self.compare_heading_right.setAlignment(QtCore.Qt.AlignCenter)
  782. self.compare_heading = QtGui.QWidget()
  783. self.compare_heading_layout = QtGui.QHBoxLayout()
  784. self.compare_heading_layout.addWidget(self.compare_heading_left)
  785. self.compare_heading_layout.addWidget(self.compare_heading_right)
  786. self.compare_heading.setLayout(self.compare_heading_layout)
  787. self.compare_heading.hide()
  788. self.adc1Compare = self.createCheckbox(text="ADC 1", connect=lambda: self.change_adc_compare(who=1))
  789. self.adc2Compare = self.createCheckbox(text="ADC 2", connect=lambda: self.change_adc_compare(who=2))
  790. self.adc3Compare = self.createCheckbox(text="ADC 3", connect=lambda: self.change_adc_compare(who=3))
  791. self.adc4Compare = self.createCheckbox(text="ADC 4", connect=lambda: self.change_adc_compare(who=4))
  792. if int(self.board_config.get('adc_number')) > 4:
  793. self.adc5Compare = self.createCheckbox(text="ADC 5", connect=lambda: self.change_adc_compare(who=8))
  794. self.adc6Compare = self.createCheckbox(text="ADC 6", connect=lambda: self.change_adc_compare(who=6))
  795. self.adc7Compare = self.createCheckbox(text="ADC 7", connect=lambda: self.change_adc_compare(who=7))
  796. self.adc8Compare = self.createCheckbox(text="ADC 8", connect=lambda: self.change_adc_compare(who=8))
  797. self.adc1Compare.setChecked(True)
  798. self.adc2Compare.setChecked(True)
  799. self.adc_checkbox_compare_layout = QtGui.QHBoxLayout()
  800. self.adc_checkbox_compare_layout.addWidget(self.adc1Compare)
  801. self.adc_checkbox_compare_layout.addWidget(self.adc2Compare)
  802. self.adc_checkbox_compare_layout.addWidget(self.adc3Compare)
  803. self.adc_checkbox_compare_layout.addWidget(self.adc4Compare)
  804. self.groupWidgetCompare = QtGui.QWidget()
  805. self.groupWidgetCompare.setLayout(self.adc_checkbox_compare_layout)
  806. self.groupCompare = QtGui.QButtonGroup()
  807. self.groupCompare.setExclusive(False)
  808. self.groupCompare.addButton(self.adc1Compare, 1)
  809. self.groupCompare.addButton(self.adc2Compare, 2)
  810. self.groupCompare.addButton(self.adc3Compare, 3)
  811. self.groupCompare.addButton(self.adc4Compare, 4)
  812. if int(self.board_config.get('adc_number')) > 4:
  813. self.groupCompare.addButton(self.adc4Compare, 5)
  814. self.groupCompare.addButton(self.adc4Compare, 6)
  815. self.groupCompare.addButton(self.adc4Compare, 7)
  816. self.groupCompare.addButton(self.adc4Compare, 8)
  817. self.groupWidgetCompare.hide()
  818. self.fft_mode = QtGui.QComboBox(self)
  819. self.fft_mode.addItem("Image") #0
  820. self.fft_mode.addItem("All") #1
  821. self.fft_mode.addItem("Mean") #1
  822. for i in range(184):
  823. self.fft_mode.addItem("Bucket {}".format(i))
  824. self.fft_mode.currentIndexChanged.connect(lambda: [self.change_adc(), self.changePlotType(self.theType)])
  825. self.fft_log = self.createCheckbox(text="Log", connect=lambda: self.change_adc())
  826. self.from_to_layout = QtGui.QHBoxLayout()
  827. self.from_spinbox = self.createSpinbox(0, 100000000, interval=100, connect=lambda: self.plot(self.theType))
  828. self.to_spinbox = self.createSpinbox(0, 100000000, start_value=1000, interval=100, connect=lambda: self.plot(self.theType))
  829. self.from_to_layout.addWidget(self.fft_mode)
  830. self.from_to_layout.addWidget(self.fft_log)
  831. self.from_to_layout.addStretch()
  832. self.from_to_layout.addWidget(self.createLabel(tr("Label", "From:")))
  833. self.from_to_layout.addWidget(self.from_spinbox)
  834. self.from_to_layout.addWidget(self.createLabel(tr("Label", "To:")))
  835. self.from_to_layout.addWidget(self.to_spinbox)
  836. self.useCalib=self.createCheckbox('use Calib', connect=lambda: self.plot(self.theType))
  837. self.from_to_layout.addWidget(self.useCalib)
  838. self.makeMean=self.createCheckbox('Mean', connect=lambda: self.plot(self.theType))
  839. self.from_to_layout.addWidget(self.makeMean)
  840. self.fittGauss=self.createCheckbox('Fitt', connect=lambda: self.plot(self.theType))
  841. self.from_to_layout.addWidget(self.fittGauss)
  842. self.layout.addLayout(self.plot_buttons_layout)
  843. self.layout.addWidget(self.compare_heading)
  844. self.layout.addWidget(self.plot_widget)
  845. self.layout.addWidget(self.groupWidget)
  846. self.layout.addWidget(self.groupWidgetCompare)
  847. self.layout.addLayout(self.from_to_layout)
  848. self.setLayout(self.layout)
  849. self.board_config.observe(self, self.observeDataSet, 'lastDataSet')
  850. def observeDataSet(self, data):
  851. self.plot_live(data=data)
  852. def change_adc(self):
  853. """
  854. Change the adc for which data is plotted
  855. :return: -
  856. """
  857. if self.theType in [PlotType.Trains, PlotType.FFT]:
  858. self.adc=[]
  859. for i, item in enumerate(self.adcCheckBox):
  860. if item.isChecked(): self.adc.extend([i])
  861. if len(self.adc) == 0:
  862. self.adc = 1
  863. self.adcCheckBox[0].setChecked(True)
  864. else:
  865. self.adc = self.group.checkedId()-1
  866. self.change_identifier_text()
  867. self.plot(self.theType)
  868. def change_adc_compare(self, who):
  869. """
  870. Change the adcs displayed in a compare plot
  871. :return:
  872. """
  873. if not self._single_adc_checked:
  874. for b in self.groupCompare.buttons():
  875. b.setChecked(False)
  876. #self.adc1Compare.setChecked(False)
  877. #self.adc2Compare.setChecked(False)
  878. #self.adc3Compare.setChecked(False)
  879. #self.adc4Compare.setChecked(False)
  880. getattr(self, 'adc'+str(who)+'Compare').setChecked(True)
  881. self._single_adc_checked = True
  882. else:
  883. checked = [i.isChecked() for i in self.groupCompare.buttons()]
  884. self._single_adc_checked = False
  885. self.adc = np.where(np.array(checked) == True)[0][0] + 1 # +1 because adcs are 1 based and indices 0
  886. self.secadc = np.where(np.array(checked) == True)[0][1] + 1 # +1 because adcs are 1 based and indices 0
  887. self.compare_heading_left.setText("ADC "+str(self.adc))
  888. self.compare_heading_right.setText("ADC "+str(self.secadc))
  889. self.change_identifier_text()
  890. if self.theType in [ERROR, NO_DATA]:
  891. self.plot(self._old_type)
  892. else:
  893. self.plot(self.theType)
  894. def disable_buttons(self, b_bool):
  895. """
  896. Disable the buttons on this widget
  897. This is not used at the moment
  898. :param b_bool: (bool) disable(False) or enable(True)
  899. :return: -
  900. """
  901. self.heatmap_button.setDisabled(b_bool)
  902. self.fft_button.setDisabled(b_bool)
  903. self.trains_button.setDisabled(b_bool)
  904. self.combined_button.setDisabled(b_bool)
  905. def changePlotType(self, type):
  906. """
  907. Change the plot type to the given type.
  908. :param type: the new type
  909. """
  910. if type not in [ERROR, NO_DATA]:
  911. self._old_type = type
  912. self.theType = type
  913. self.plot_widget.changeType(type)
  914. for btype, button in self.type_buttons.items():
  915. style = ""
  916. if btype == type:
  917. style += "QPushButton {background-color:lightgreen;}"
  918. else:
  919. style += self.defaultButtonStyleSheet
  920. style += "QPushButton:focus{background-color: lightgrey!important; border-color: lightblue;}" # Note: this does not work
  921. button.setStyleSheet(style)
  922. if self.theType == PlotType.Trains:
  923. self.group.setExclusive(False)
  924. else:
  925. for item in self.adcCheckBox:
  926. item.setChecked(False)
  927. self.adcCheckBox[0].setChecked(True)
  928. self.group.setExclusive(True)
  929. if self.theType == PlotType.Peak:
  930. self.makeMean.show()
  931. self.fittGauss.show()
  932. else:
  933. self.makeMean.hide()
  934. self.fittGauss.hide()
  935. if self.theType == PlotType.FFT:
  936. self.fft_mode.show()
  937. self.fft_log.show()
  938. if self.fft_mode.currentIndex() > 0:
  939. self.group.setExclusive(False)
  940. else:
  941. self.group.setExclusive(True)
  942. else:
  943. self.fft_mode.hide()
  944. self.fft_log.hide()
  945. def plot(self, type=None):
  946. """
  947. Wrapper function to call the correct plot function depending on type
  948. :param type: (int) the plot type
  949. :return: -
  950. """
  951. try:
  952. if type == None:
  953. if self.theType in [ERROR, NO_DATA]:
  954. type = self._old_type
  955. else:
  956. type = self.theType
  957. self.do_plot(type)
  958. except Exception as e:
  959. print('plot wrapper except')
  960. traceback.print_exc()
  961. if self.data is None or len(self.data.array) == 0:
  962. self.changePlotType(NO_DATA)
  963. else:
  964. self.changePlotType(ERROR)
  965. self.plot_widget.plot(None)
  966. def plot_live(self, type=None, data=None):
  967. """
  968. Function to call when livedata is to be plotted
  969. :param type: (int) plot type
  970. :param data: (dataset.DataSet) data to plot
  971. :return: -
  972. """
  973. self.data = data
  974. try:
  975. if type == None:
  976. if self.theType in [ERROR, NO_DATA]:
  977. type = self._old_type
  978. else:
  979. type = self.theType
  980. if type == PlotType.Trains or type == PlotType.Combined:
  981. self.do_plot(type, autorange=True)
  982. else:
  983. self.do_plot(type, autorange=False)
  984. except Exception:
  985. traceback.print_exc()
  986. if len(self.data.array) == 0:
  987. self.changePlotType(NO_DATA)
  988. else:
  989. self.changePlotType(ERROR)
  990. self.plot_widget.plot(None)
  991. def do_plot(self, type, autorange=True):
  992. #print(time.time(), 'do_plot start')
  993. """
  994. Actually perform a plot (this calls SubPlotWidget.plot)
  995. :param type: (int) plot type
  996. :param autorange: (bool) whether to perform a autorange upon plot
  997. :return: -
  998. """
  999. if self.theType != type:
  1000. if type == PlotType.Combined:
  1001. self.groupWidget.hide()
  1002. elif self.theType == PlotType.Combined:
  1003. self.groupWidget.show()
  1004. if type == PlotType.Compare:
  1005. self.groupWidget.hide()
  1006. self.groupWidgetCompare.show()
  1007. self.compare_heading.show()
  1008. else:
  1009. self.groupWidget.show()
  1010. self.groupWidgetCompare.hide()
  1011. self.compare_heading.hide()
  1012. self.changePlotType(type)
  1013. self.setWindowTitle(self.theName + " - " + plotList[type] + " - " + str(self.thePrefix))
  1014. self.change_identifier_text()
  1015. if self.theType == PlotType.Compare:
  1016. self.change_identifier_text()
  1017. if self.theType == PlotType.Compare:
  1018. self.groupWidget.hide()
  1019. self.groupWidgetCompare.show()
  1020. self.compare_heading.show()
  1021. else:
  1022. self.groupWidgetCompare.hide()
  1023. self.compare_heading.hide()
  1024. f = self.from_spinbox.value()
  1025. t = self.to_spinbox.value() if self.to_spinbox.value() > f else f+1
  1026. if type == PlotType.FFT:
  1027. self.plot_widget.plot(self.data.fft(adc=self.adc, frm=f, to=t+1, drop_first_bin=True, nobunching=self.fft_mode.currentIndex()==1),
  1028. autorange=autorange,
  1029. xvalueborders=[self.data.fftFreqDist(), self.data.fftMaxFreq()],
  1030. fft_mode = self.fft_mode.currentIndex(),
  1031. log=self.fft_log.isChecked())
  1032. if type == PlotType.Heatmap:
  1033. self.plot_widget.plot( self.data.heatmap(adc=self.adc, frm=f, to=t).transpose(), autorange=autorange)
  1034. if type == PlotType.Compare:
  1035. 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)
  1036. if type == PlotType.Trains:
  1037. self.plot_widget.plot(self.data.train(adc=self.adc, frm=f, to=t, calibrate=self.useCalib.isChecked()), autorange=autorange)
  1038. if type == PlotType.Combined:
  1039. self.plot_widget.plot(self.data.combined(adc=self.adc, frm=f, to=t, calibrate=self.useCalib.isChecked(), workingChannels=config.working_channels), autorange=autorange)
  1040. if type == PlotType.Peak:
  1041. t = t+184
  1042. self.plot_widget.plot(self.data.combined(adc=self.adc, frm=f, to=t, calibrate=self.useCalib.isChecked(), workingChannels=config.working_channels, turnbyturn=False, mean=self.makeMean.isChecked()), autorange=autorange, doFit=(6 if self.fittGauss.isChecked() else 0))
  1043. #print(time.time(), 'do_plot stop'))
  1044. def change_identifier_text(self):
  1045. """
  1046. Change the text that identifies the plot in the left bar
  1047. :return:
  1048. """
  1049. if self.theType is PlotType.Compare:
  1050. the_text = str(self.adc)+"+"+str(self.secadc)
  1051. else:
  1052. the_text = str(self.adc)
  1053. if self.theType in [ERROR, NO_DATA]:
  1054. type = self._old_type
  1055. else:
  1056. type = self.theType
  1057. if self.theDataType is LIVE:
  1058. self.change_type_signal.emit(self.theId, type,
  1059. the_text+" B: "+available_boards.get_board_name_from_id(self.board_id))
  1060. else:
  1061. self.change_type_signal.emit(self.theId, type, the_text)
  1062. def closeEvent(self, event):
  1063. """
  1064. Event Handler to handle the event of closing this window
  1065. :param event: QEvent
  1066. :return: -
  1067. """
  1068. self.board_config.unobserve(self, 'lastDataSet')
  1069. if not self.close_silent:
  1070. if not self.parent.remove_plot(self.theId, silent=self.close_silent):
  1071. event.ignore()
  1072. return
  1073. del self.data
  1074. del self.plot_widget
  1075. super(PlotWidget, self).closeEvent(event)
  1076. if self.theDataType == LIVE:
  1077. live_plot_windows.removeWindow(self.board_id, self)
  1078. self.close_signal.emit()
  1079. del self