mirror of
https://github.com/slsdetectorgroup/slsDetectorPackage.git
synced 2026-02-18 02:48:41 +01:00
Dev/pyctbgui merge (#960)
* added empty c extension * added rotation to the decoding * added color map, options and findex * minor * move checks to before acquisition * added pixel map based decoder * cleanup * no thread creation for single thread processing * added rotation and test to compare * allow high and low water mark for zmq (also buffer size) for fast readouts * removed roatation during decoding * added Transpose to image and invert Y False to invert it * retains the zoomed state after the first image of gui, catch and display exception if no detector connected * moved start frame to dockable widget, removed extra frame number label, moved current measurement also to dockable widget, hide frame plot entirely when showing patternviewer * first image dependin on which plot * remember settings of main window size and position, dockewidget if docked, its size and posisiotn as well, then update it next time the gui is opened * change in comment * using c decoder for moench 04 and matterhorn * catch exception from invalid image from decoder * clean up * update row and col when choosing image type, neeeded to show values * fix for previous PR * disable resetting colormap values keep the range selected for every new acquisition * fix typos + tested on virtual matterhorn * minor print * refactored Slow ADCs Tab * refactored DAC tab * refactored power supplies * refactored signals tab * refactored transceiver tab * fix typo * fix typo2 * remove commented code * delete commented code * delete commented code * delete commented signals code * remove commented code for transceiver tab * refactor adc tab * refactor Pattern Tab * Refactor transceivers tab (PR#5) (#118) * refactored transceiver tab * remove commented code for transceiver tab --------- Co-authored-by: Erik Frojdh <erik.frojdh@gmail.com> * refactor adc tab (PR#6) (#119) * refactor adc tab * refactored Plot and Acquisition Tabs * fix the regression issue * restructure project files * applying singleton and renaming tabs to services * working install using pip * applies singleton to tab classes and remove CI erros * added pyzmq and pillow * remove the singleton implementation and keep changes * fix merge errors in mainWindow * moved misplaced init file * rename service to tab * reorganize imports * iterate over tabs * reorder tabs * add slowadc to the list * saving changes (buggy) power supply ui not showing in the gui * split power supply tab * fixed tests * add hardcoded values to defines file * fix error * separate power supply * fix errors for powerSuppliesTab * split dacs * split slow adcs * split signals tab * added tests for bit_utils * add slowAdc class to defines * split transceiver ui file * split adc.ui * split pattern ui file * split plot and acquisition ui file * added basic test for parsing bit names * removed redundant code in read_alias_file * fix dacs ui position * testing for correct exception * cmd and args at split * group radio buttons * fix comments from PR#1 * show legend * added python version and dev requirements to setup.py * fix dac issue * moved _decoder into pkg * added inplace build * removed clear * fixed dependencies * make tests run without slsdet * updated name of action * define colcount * fixed loading of alias file * add yapf and ruff * apply formatting * fix E and F rules * add more ruff rules * change variable name * squashing gh debugging commits and add pre-commit * update label values to mv units * add hook for yapf * reconfigure yapf precommit hook * add format and check_format to makefile * change gh actions * update readme * added check_format * WIP * added linting in github action * updated readme] * add more control for color choice * remove useless file * fix un-updated line after refactoring Defines BIT0_31_MASK is not found in Defines.signals * visually improve the interface * fix last commit * add only selected plots for legend * add hide legend button * change hide legend to show legend checkbox show legend is checked by default * add support for saving in numpy * solve conversations * fix acq index offset * fix browse button in pattern error * fix other browse button errors * finish tests and add usage.md * remove buffer * add file,numpy-like interface and tests * remove useless .npy files * subscriptible npz files * remove useless files * remove repetetive tests * save changes * add mode r+, add with support,remove logging * remove offset of acqIndex between raw and numpy saving * fix only saving last frame * save signals of multiple devices * add comments and move condition for clearer code * fix bug when vieweing pattern file * iterate over enabled and plotted plots * add padestal substraction to transceiver and analog data * init pedestal frames to detector.frames * restore old exception * add pedestal substraction for digital signals * remove frames spinbox from plotTab * remove comments and use str instead of Path * avoid saving all frames * correct exception and log error's trace * add gui tests * add waveform test * add pedestal test * refactor by using fixtures * add tests for moench analog and pattern * add pytest-qt to dependencies * add save and load gui parameters * remove nohup file * fix old bug IndexError * save plot type * a * handle canceling load, loading matterhorn pedestal for moench * remove comparing .png files for pattern test * save plot type * red error on status bar when shape mismatch for loaded pedestal * fix makefile and docstrings * fix PRs conversation * move code into different function * fix wrong function names for power supply * removed old ctbgui * removed unnecessary files --------- Co-authored-by: Erik Frojdh <erik.frojdh@gmail.com> Co-authored-by: Braham Bechir <braham_b@pc11979.psi.ch> Co-authored-by: Bechir <bechir.braham@psi.ch> Co-authored-by: Bechir <bechir.brahem420@gmail.com>
This commit is contained in:
401
pyctbgui/pyctbgui/services/ADC.py
Normal file
401
pyctbgui/pyctbgui/services/ADC.py
Normal file
@@ -0,0 +1,401 @@
|
||||
import logging
|
||||
import typing
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
from PyQt5 import QtWidgets, uic
|
||||
import pyqtgraph as pg
|
||||
from pyqtgraph import LegendItem
|
||||
|
||||
from pyctbgui.utils import decoder
|
||||
from pyctbgui.utils.bit_utils import bit_is_set, manipulate_bit
|
||||
from pyctbgui.utils.defines import Defines
|
||||
import pyctbgui.utils.pixelmap as pm
|
||||
from pyctbgui.utils.recordOrApplyPedestal import recordOrApplyPedestal
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from pyctbgui.services import AcquisitionTab, PlotTab
|
||||
|
||||
|
||||
class AdcTab(QtWidgets.QWidget):
|
||||
|
||||
def __init__(self, parent, *args, **kwargs):
|
||||
super().__init__(parent, *args, **kwargs)
|
||||
uic.loadUi(Path(__file__).parent.parent / 'ui' / "adc.ui", parent)
|
||||
self.view = parent
|
||||
self.mainWindow = None
|
||||
self.det = None
|
||||
self.plotTab: PlotTab | None = None
|
||||
self.acquisitionTab: AcquisitionTab | None = None
|
||||
self.legend: LegendItem | None = None
|
||||
self.logger = logging.getLogger('AdcTab')
|
||||
|
||||
def setup_ui(self):
|
||||
self.plotTab = self.mainWindow.plotTab
|
||||
self.acquisitionTab = self.mainWindow.acquisitionTab
|
||||
for i in range(Defines.adc.count):
|
||||
self.setADCButtonColor(i, self.plotTab.getRandomColor())
|
||||
self.initializeAllAnalogPlots()
|
||||
|
||||
self.legend = self.mainWindow.plotAnalogWaveform.getPlotItem().legend
|
||||
self.legend.clear()
|
||||
# subscribe to toggle legend
|
||||
self.plotTab.subscribeToggleLegend(self.updateLegend)
|
||||
|
||||
def initializeAllAnalogPlots(self):
|
||||
self.mainWindow.plotAnalogWaveform = pg.plot()
|
||||
self.mainWindow.plotAnalogWaveform.addLegend(colCount=Defines.colCount)
|
||||
self.mainWindow.verticalLayoutPlot.addWidget(self.mainWindow.plotAnalogWaveform, 1)
|
||||
self.mainWindow.analogPlots = {}
|
||||
waveform = np.zeros(1000)
|
||||
for i in range(Defines.adc.count):
|
||||
pen = pg.mkPen(color=self.getADCButtonColor(i), width=1)
|
||||
legendName = getattr(self.view, f"labelADC{i}").text()
|
||||
self.mainWindow.analogPlots[i] = self.mainWindow.plotAnalogWaveform.plot(waveform,
|
||||
pen=pen,
|
||||
name=legendName)
|
||||
self.mainWindow.analogPlots[i].hide()
|
||||
|
||||
self.mainWindow.plotAnalogImage = pg.ImageView()
|
||||
self.mainWindow.nAnalogRows = 0
|
||||
self.mainWindow.nAnalogCols = 0
|
||||
self.mainWindow.analog_frame = np.zeros((self.mainWindow.nAnalogRows, self.mainWindow.nAnalogCols))
|
||||
self.mainWindow.plotAnalogImage.getView().invertY(False)
|
||||
self.mainWindow.plotAnalogImage.setImage(self.mainWindow.analog_frame)
|
||||
self.mainWindow.verticalLayoutPlot.addWidget(self.mainWindow.plotAnalogImage, 2)
|
||||
|
||||
def connect_ui(self):
|
||||
for i in range(Defines.adc.count):
|
||||
getattr(self.view, f"checkBoxADC{i}Inv").stateChanged.connect(partial(self.setADCInv, i))
|
||||
getattr(self.view, f"checkBoxADC{i}En").stateChanged.connect(partial(self.setADCEnable, i))
|
||||
getattr(self.view, f"checkBoxADC{i}Plot").stateChanged.connect(partial(self.setADCEnablePlot, i))
|
||||
getattr(self.view, f"pushButtonADC{i}").clicked.connect(partial(self.selectADCColor, i))
|
||||
self.view.checkBoxADC0_15En.stateChanged.connect(partial(self.setADCEnableRange, 0, Defines.adc.half))
|
||||
self.view.checkBoxADC16_31En.stateChanged.connect(
|
||||
partial(self.setADCEnableRange, Defines.adc.half, Defines.adc.count))
|
||||
self.view.checkBoxADC0_15Plot.stateChanged.connect(partial(self.setADCEnablePlotRange, 0, Defines.adc.half))
|
||||
self.view.checkBoxADC16_31Plot.stateChanged.connect(
|
||||
partial(self.setADCEnablePlotRange, Defines.adc.half, Defines.adc.count))
|
||||
self.view.checkBoxADC0_15Inv.stateChanged.connect(partial(self.setADCInvRange, 0, Defines.adc.half))
|
||||
self.view.checkBoxADC16_31Inv.stateChanged.connect(
|
||||
partial(self.setADCInvRange, Defines.adc.half, Defines.adc.count))
|
||||
self.view.lineEditADCInversion.editingFinished.connect(self.setADCInvReg)
|
||||
self.view.lineEditADCEnable.editingFinished.connect(self.setADCEnableReg)
|
||||
|
||||
def refresh(self):
|
||||
self.updateADCNames()
|
||||
self.updateADCInv()
|
||||
self.updateADCEnable()
|
||||
|
||||
# ADCs Tab functions
|
||||
|
||||
def getEnabledPlots(self):
|
||||
"""
|
||||
return plots that are shown (checkBoxADC#Plot is checked)
|
||||
"""
|
||||
enabledPlots = []
|
||||
self.legend.clear()
|
||||
for i in range(Defines.adc.count):
|
||||
if getattr(self.view, f'checkBoxADC{i}Plot').isChecked():
|
||||
plotName = getattr(self.view, f"labelADC{i}").text()
|
||||
enabledPlots.append((self.mainWindow.analogPlots[i], plotName))
|
||||
return enabledPlots
|
||||
|
||||
def updateLegend(self):
|
||||
"""
|
||||
update the legend for the ADC waveform plot
|
||||
should be called after checking or unchecking plot checkbox
|
||||
"""
|
||||
if not self.mainWindow.showLegend:
|
||||
self.legend.clear()
|
||||
else:
|
||||
for plot, name in self.getEnabledPlots():
|
||||
self.legend.addItem(plot, name)
|
||||
|
||||
def updateADCNames(self):
|
||||
"""
|
||||
get adc names from detector and update them in the UI
|
||||
"""
|
||||
for i, adc_name in enumerate(self.det.getAdcNames()):
|
||||
getattr(self.view, f"labelADC{i}").setText(adc_name)
|
||||
|
||||
def processWaveformData(self, data: bytes, aSamples: int) -> dict[str, np.ndarray]:
|
||||
"""
|
||||
view function
|
||||
plots processed waveform data
|
||||
@param data: raw waveform data
|
||||
@param aSamples: analog samples
|
||||
@return: waveform dict returned to handle it for saving the output
|
||||
"""
|
||||
|
||||
waveforms = {}
|
||||
analog_array = self._processWaveformData(data, aSamples, self.mainWindow.nADCEnabled)
|
||||
idx = 0
|
||||
for i in range(Defines.adc.count):
|
||||
checkBoxPlot = getattr(self.view, f"checkBoxADC{i}Plot")
|
||||
checkBoxEn = getattr(self.view, f"checkBoxADC{i}En")
|
||||
|
||||
if checkBoxEn.isChecked() and checkBoxPlot.isChecked():
|
||||
waveform = analog_array[:, idx]
|
||||
idx += 1
|
||||
self.mainWindow.analogPlots[i].setData(waveform)
|
||||
plotName = getattr(self.view, f"labelADC{i}").text()
|
||||
waveforms[plotName] = waveform
|
||||
return waveforms
|
||||
|
||||
@recordOrApplyPedestal
|
||||
def _processWaveformData(self, data: bytes, aSamples: int, nADCEnabled: int) -> np.ndarray:
|
||||
"""
|
||||
model function
|
||||
processes raw waveform data
|
||||
@param data: raw waveform data
|
||||
@param aSamples: analog samples
|
||||
@param nADCEnabled: number of enabled ADCs
|
||||
@return: processed waveform data
|
||||
"""
|
||||
analog_array = np.array(np.frombuffer(data, dtype=np.uint16, count=nADCEnabled * aSamples))
|
||||
return analog_array.reshape(-1, nADCEnabled)
|
||||
|
||||
def processImageData(self, data, aSamples):
|
||||
"""
|
||||
process the raw receiver data for analog image
|
||||
data: raw analog image
|
||||
aSamples: analog samples
|
||||
"""
|
||||
# get zoom state
|
||||
viewBox = self.mainWindow.plotAnalogImage.getView()
|
||||
state = viewBox.getState()
|
||||
try:
|
||||
self.mainWindow.analog_frame = self._processImageData(data, aSamples, self.mainWindow.nADCEnabled)
|
||||
self.plotTab.ignoreHistogramSignal = True
|
||||
self.mainWindow.plotAnalogImage.setImage(self.mainWindow.analog_frame.T)
|
||||
except Exception:
|
||||
self.logger.exception('Exception Caught')
|
||||
self.mainWindow.statusbar.setStyleSheet("color:red")
|
||||
message = f'Warning: Invalid size for Analog Image. Expected' \
|
||||
f' {self.mainWindow.nAnalogRows * self.mainWindow.nAnalogCols} ' \
|
||||
f'size, got {self.mainWindow.analog_frame.size} instead.'
|
||||
self.acquisitionTab.updateCurrentFrame('Invalid Image')
|
||||
|
||||
self.mainWindow.statusbar.showMessage(message)
|
||||
print(message)
|
||||
|
||||
self.plotTab.setFrameLimits(self.mainWindow.analog_frame)
|
||||
|
||||
# keep the zoomed in state (not 1st image)
|
||||
if self.mainWindow.firstAnalogImage:
|
||||
self.mainWindow.firstAnalogImage = False
|
||||
else:
|
||||
viewBox.setState(state)
|
||||
return self.mainWindow.analog_frame.T
|
||||
|
||||
@recordOrApplyPedestal
|
||||
def _processImageData(self, data, aSamples, nADCEnabled):
|
||||
analog_array = np.array(np.frombuffer(data, dtype=np.uint16, count=nADCEnabled * aSamples))
|
||||
return decoder.decode(analog_array, pm.moench04_analog())
|
||||
|
||||
def getADCEnableReg(self):
|
||||
retval = self.det.adcenable
|
||||
if self.det.tengiga:
|
||||
retval = self.det.adcenable10g
|
||||
self.view.lineEditADCEnable.editingFinished.disconnect()
|
||||
self.view.lineEditADCEnable.setText("0x{:08x}".format(retval))
|
||||
self.view.lineEditADCEnable.editingFinished.connect(self.setADCEnableReg)
|
||||
return retval
|
||||
|
||||
def setADCEnableReg(self):
|
||||
self.view.lineEditADCEnable.editingFinished.disconnect()
|
||||
try:
|
||||
mask = int(self.mainWindow.lineEditADCEnable.text(), 16)
|
||||
if self.det.tengiga:
|
||||
self.det.adcenable10g = mask
|
||||
else:
|
||||
self.det.adcenable = mask
|
||||
except Exception as e:
|
||||
QtWidgets.QMessageBox.warning(self.mainWindow, "ADC Enable Fail", str(e), QtWidgets.QMessageBox.Ok)
|
||||
pass
|
||||
# TODO: handling double event exceptions
|
||||
self.view.lineEditADCEnable.editingFinished.connect(self.setADCEnableReg)
|
||||
self.updateADCEnable()
|
||||
|
||||
def getADCEnable(self, i, mask):
|
||||
checkBox = getattr(self.view, f"checkBoxADC{i}En")
|
||||
checkBox.stateChanged.disconnect()
|
||||
checkBox.setChecked(bit_is_set(mask, i))
|
||||
checkBox.stateChanged.connect(partial(self.setADCEnable, i))
|
||||
|
||||
def updateADCEnable(self):
|
||||
retval = self.getADCEnableReg()
|
||||
self.mainWindow.nADCEnabled = bin(retval).count('1')
|
||||
for i in range(Defines.adc.count):
|
||||
self.getADCEnable(i, retval)
|
||||
self.getADCEnablePlot(i)
|
||||
self.getADCEnableColor(i)
|
||||
self.plotTab.addSelectedAnalogPlots(i)
|
||||
self.getADCEnableRange(retval)
|
||||
self.getADCEnablePlotRange()
|
||||
|
||||
def setADCEnable(self, i):
|
||||
checkBox = getattr(self.view, f"checkBoxADC{i}En")
|
||||
try:
|
||||
if self.det.tengiga:
|
||||
enableMask = manipulate_bit(checkBox.isChecked(), self.det.adcenable10g, i)
|
||||
self.det.adcenable10g = enableMask
|
||||
else:
|
||||
enableMask = manipulate_bit(checkBox.isChecked(), self.det.adcenable, i)
|
||||
self.det.adcenable = enableMask
|
||||
except Exception as e:
|
||||
QtWidgets.QMessageBox.warning(self.mainWindow, "ADC Enable Fail", str(e), QtWidgets.QMessageBox.Ok)
|
||||
|
||||
self.updateADCEnable()
|
||||
|
||||
def getADCEnableRange(self, mask):
|
||||
self.view.checkBoxADC0_15En.stateChanged.disconnect()
|
||||
self.view.checkBoxADC16_31En.stateChanged.disconnect()
|
||||
self.view.checkBoxADC0_15En.setChecked((mask & Defines.adc.BIT0_15_MASK) == Defines.adc.BIT0_15_MASK)
|
||||
self.view.checkBoxADC16_31En.setChecked((mask & Defines.adc.BIT16_31_MASK) == Defines.adc.BIT16_31_MASK)
|
||||
self.view.checkBoxADC0_15En.stateChanged.connect(partial(self.setADCEnableRange, 0, Defines.adc.half))
|
||||
self.view.checkBoxADC16_31En.stateChanged.connect(
|
||||
partial(self.setADCEnableRange, Defines.adc.half, Defines.adc.count))
|
||||
|
||||
def setADCEnableRange(self, start_nr, end_nr):
|
||||
mask = self.getADCEnableReg()
|
||||
checkBox = getattr(self.view, f"checkBoxADC{start_nr}_{end_nr - 1}En")
|
||||
for i in range(start_nr, end_nr):
|
||||
mask = manipulate_bit(checkBox.isChecked(), mask, i)
|
||||
try:
|
||||
if self.det.tengiga:
|
||||
self.det.adcenable10g = mask
|
||||
else:
|
||||
self.det.adcenable = mask
|
||||
except Exception as e:
|
||||
QtWidgets.QMessageBox.warning(self.mainWindow, "ADC Enable Fail", str(e), QtWidgets.QMessageBox.Ok)
|
||||
pass
|
||||
self.updateADCEnable()
|
||||
|
||||
def getADCEnablePlot(self, i):
|
||||
checkBox = getattr(self.view, f"checkBoxADC{i}En")
|
||||
checkBoxPlot = getattr(self.view, f"checkBoxADC{i}Plot")
|
||||
checkBoxPlot.setEnabled(checkBox.isChecked())
|
||||
|
||||
def setADCEnablePlot(self, i):
|
||||
pushButton = getattr(self.view, f"pushButtonADC{i}")
|
||||
checkBox = getattr(self.view, f"checkBoxADC{i}Plot")
|
||||
pushButton.setEnabled(checkBox.isChecked())
|
||||
|
||||
self.getADCEnablePlotRange()
|
||||
self.plotTab.addSelectedAnalogPlots(i)
|
||||
self.updateLegend()
|
||||
|
||||
def getADCEnablePlotRange(self):
|
||||
self.view.checkBoxADC0_15Plot.stateChanged.disconnect()
|
||||
self.view.checkBoxADC16_31Plot.stateChanged.disconnect()
|
||||
self.view.checkBoxADC0_15Plot.setEnabled(
|
||||
all(getattr(self.view, f"checkBoxADC{i}Plot").isEnabled() for i in range(Defines.adc.half)))
|
||||
self.view.checkBoxADC16_31Plot.setEnabled(
|
||||
all(
|
||||
getattr(self.view, f"checkBoxADC{i}Plot").isEnabled()
|
||||
for i in range(Defines.adc.half, Defines.adc.count)))
|
||||
self.view.checkBoxADC0_15Plot.setChecked(
|
||||
all(getattr(self.view, f"checkBoxADC{i}Plot").isChecked() for i in range(Defines.adc.half)))
|
||||
self.view.checkBoxADC16_31Plot.setChecked(
|
||||
all(
|
||||
getattr(self.view, f"checkBoxADC{i}Plot").isChecked()
|
||||
for i in range(Defines.adc.half, Defines.adc.count)))
|
||||
self.view.checkBoxADC0_15Plot.stateChanged.connect(partial(self.setADCEnablePlotRange, 0, Defines.adc.half))
|
||||
self.view.checkBoxADC16_31Plot.stateChanged.connect(
|
||||
partial(self.setADCEnablePlotRange, Defines.adc.half, Defines.adc.count))
|
||||
|
||||
def setADCEnablePlotRange(self, start_nr, end_nr):
|
||||
checkBox = getattr(self.view, f"checkBoxADC{start_nr}_{end_nr - 1}Plot")
|
||||
enable = checkBox.isChecked()
|
||||
for i in range(start_nr, end_nr):
|
||||
checkBox = getattr(self.view, f"checkBoxADC{i}Plot")
|
||||
checkBox.setChecked(enable)
|
||||
self.plotTab.addAllSelectedAnalogPlots()
|
||||
|
||||
def getADCEnableColor(self, i):
|
||||
checkBox = getattr(self.view, f"checkBoxADC{i}Plot")
|
||||
pushButton = getattr(self.view, f"pushButtonADC{i}")
|
||||
pushButton.setEnabled(checkBox.isEnabled() and checkBox.isChecked())
|
||||
|
||||
def selectADCColor(self, i):
|
||||
pushButton = getattr(self.view, f"pushButtonADC{i}")
|
||||
self.plotTab.showPalette(pushButton)
|
||||
pen = pg.mkPen(color=self.getADCButtonColor(i), width=1)
|
||||
self.mainWindow.analogPlots[i].setPen(pen)
|
||||
|
||||
def getADCButtonColor(self, i):
|
||||
pushButton = getattr(self.view, f"pushButtonADC{i}")
|
||||
return self.plotTab.getActiveColor(pushButton)
|
||||
|
||||
def setADCButtonColor(self, i, color):
|
||||
pushButton = getattr(self.view, f"pushButtonADC{i}")
|
||||
return self.plotTab.setActiveColor(pushButton, color)
|
||||
|
||||
def getADCInvReg(self):
|
||||
retval = self.det.adcinvert
|
||||
self.view.lineEditADCInversion.editingFinished.disconnect()
|
||||
self.view.lineEditADCInversion.setText("0x{:08x}".format(retval))
|
||||
self.view.lineEditADCInversion.editingFinished.connect(self.setADCInvReg)
|
||||
return retval
|
||||
|
||||
def setADCInvReg(self):
|
||||
self.view.lineEditADCInversion.editingFinished.disconnect()
|
||||
try:
|
||||
self.det.adcinvert = int(self.mainWindow.lineEditADCInversion.text(), 16)
|
||||
except Exception as e:
|
||||
QtWidgets.QMessageBox.warning(self.mainWindow, "ADC Inversion Fail", str(e), QtWidgets.QMessageBox.Ok)
|
||||
pass
|
||||
# TODO: handling double event exceptions
|
||||
self.view.lineEditADCInversion.editingFinished.connect(self.setADCInvReg)
|
||||
self.updateADCInv()
|
||||
|
||||
def getADCInv(self, i, inv):
|
||||
checkBox = getattr(self.view, f"checkBoxADC{i}Inv")
|
||||
checkBox.stateChanged.disconnect()
|
||||
checkBox.setChecked(bit_is_set(inv, i))
|
||||
checkBox.stateChanged.connect(partial(self.setADCInv, i))
|
||||
|
||||
def updateADCInv(self):
|
||||
retval = self.getADCInvReg()
|
||||
for i in range(Defines.adc.count):
|
||||
self.getADCInv(i, retval)
|
||||
self.getADCInvRange(retval)
|
||||
|
||||
def setADCInv(self, i):
|
||||
out = self.det.adcinvert
|
||||
checkBox = getattr(self.view, f"checkBoxADC{i}Inv")
|
||||
mask = manipulate_bit(checkBox.isChecked(), out, i)
|
||||
self.det.adcinvert = mask
|
||||
|
||||
retval = self.getADCInvReg()
|
||||
self.getADCInv(i, retval)
|
||||
self.getADCInvRange(retval)
|
||||
|
||||
def getADCInvRange(self, inv):
|
||||
self.view.checkBoxADC0_15Inv.stateChanged.disconnect()
|
||||
self.view.checkBoxADC16_31Inv.stateChanged.disconnect()
|
||||
self.view.checkBoxADC0_15Inv.setChecked((inv & Defines.adc.BIT0_15_MASK) == Defines.adc.BIT0_15_MASK)
|
||||
self.view.checkBoxADC16_31Inv.setChecked((inv & Defines.adc.BIT16_31_MASK) == Defines.adc.BIT16_31_MASK)
|
||||
self.view.checkBoxADC0_15Inv.stateChanged.connect(partial(self.setADCInvRange, 0, Defines.adc.half))
|
||||
self.view.checkBoxADC16_31Inv.stateChanged.connect(
|
||||
partial(self.setADCInvRange, Defines.adc.half, Defines.adc.count))
|
||||
|
||||
def setADCInvRange(self, start_nr, end_nr):
|
||||
out = self.det.adcinvert
|
||||
checkBox = getattr(self.view, f"checkBoxADC{start_nr}_{end_nr - 1}Inv")
|
||||
mask = getattr(Defines.adc, f"BIT{start_nr}_{end_nr - 1}_MASK")
|
||||
if checkBox.isChecked():
|
||||
self.det.adcinvert = out | mask
|
||||
else:
|
||||
self.det.adcinvert = out & ~mask
|
||||
|
||||
self.updateADCInv()
|
||||
|
||||
def saveParameters(self) -> list[str]:
|
||||
return [
|
||||
f"adcenable {self.view.lineEditADCEnable.text()}",
|
||||
f"adcinvert {self.view.lineEditADCInversion.text()}",
|
||||
]
|
||||
719
pyctbgui/pyctbgui/services/Acquisition.py
Normal file
719
pyctbgui/pyctbgui/services/Acquisition.py
Normal file
@@ -0,0 +1,719 @@
|
||||
import json
|
||||
import typing
|
||||
from pathlib import Path
|
||||
import numpy as np
|
||||
import time
|
||||
import zmq
|
||||
from PyQt5 import QtWidgets, uic
|
||||
import logging
|
||||
|
||||
from slsdet import readoutMode, runStatus
|
||||
from pyctbgui.utils.defines import Defines
|
||||
from pyctbgui.utils.numpyWriter.npy_writer import NumpyFileManager
|
||||
from pyctbgui.utils.numpyWriter.npz_writer import NpzFileWriter
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
# only used for type hinting. To avoid circular dependencies these
|
||||
# won't be imported in runtime
|
||||
from pyctbgui.services import SignalsTab, TransceiverTab, AdcTab, PlotTab
|
||||
|
||||
|
||||
class AcquisitionTab(QtWidgets.QWidget):
|
||||
|
||||
def __init__(self, parent):
|
||||
self.__isWaveform = None
|
||||
super().__init__(parent)
|
||||
self.currentMeasurement = None
|
||||
self.dsamples = None
|
||||
self.stoppedFlag = None
|
||||
self.asamples = None
|
||||
self.tsamples = None
|
||||
uic.loadUi(Path(__file__).parent.parent / 'ui' / "acquisition.ui", parent)
|
||||
self.view = parent
|
||||
self.mainWindow = None
|
||||
self.det = None
|
||||
self.signalsTab: SignalsTab = None
|
||||
self.transceiverTab: TransceiverTab = None
|
||||
self.adcTab: AdcTab = None
|
||||
self.plotTab: PlotTab = None
|
||||
self.writeNumpy: bool = False
|
||||
self.outputDir: Path = Path('/')
|
||||
self.outputFileNamePrefix: str = ''
|
||||
self.numpyFileManagers: dict[str, NumpyFileManager] = {}
|
||||
|
||||
self.logger = logging.getLogger('AcquisitionTab')
|
||||
|
||||
def setup_ui(self):
|
||||
self.signalsTab = self.mainWindow.signalsTab
|
||||
self.transceiverTab = self.mainWindow.transceiverTab
|
||||
self.adcTab = self.mainWindow.adcTab
|
||||
self.plotTab = self.mainWindow.plotTab
|
||||
self.toggleStartButton(False)
|
||||
|
||||
def connect_ui(self):
|
||||
# For Acquistions Tab
|
||||
self.view.comboBoxROMode.currentIndexChanged.connect(self.setReadOut)
|
||||
self.view.spinBoxRunF.editingFinished.connect(self.setRunFrequency)
|
||||
self.view.spinBoxTransceiver.editingFinished.connect(self.setTransceiver)
|
||||
self.view.spinBoxAnalog.editingFinished.connect(self.setAnalog)
|
||||
self.view.spinBoxDigital.editingFinished.connect(self.setDigital)
|
||||
self.view.spinBoxADCF.editingFinished.connect(self.setADCFrequency)
|
||||
self.view.spinBoxADCPhase.editingFinished.connect(self.setADCPhase)
|
||||
self.view.spinBoxADCPipeline.editingFinished.connect(self.setADCPipeline)
|
||||
self.view.spinBoxDBITF.editingFinished.connect(self.setDBITFrequency)
|
||||
self.view.spinBoxDBITPhase.editingFinished.connect(self.setDBITPhase)
|
||||
self.view.spinBoxDBITPipeline.editingFinished.connect(self.setDBITPipeline)
|
||||
|
||||
self.view.checkBoxFileWriteRaw.stateChanged.connect(self.setFileWrite)
|
||||
self.view.checkBoxFileWriteNumpy.stateChanged.connect(self.setFileWriteNumpy)
|
||||
self.view.lineEditFileName.editingFinished.connect(self.setFileName)
|
||||
self.view.lineEditFilePath.editingFinished.connect(self.setFilePath)
|
||||
self.view.pushButtonFilePath.clicked.connect(self.browseFilePath)
|
||||
self.view.spinBoxAcquisitionIndex.editingFinished.connect(self.setAccquisitionIndex)
|
||||
self.view.spinBoxFrames.editingFinished.connect(self.setFrames)
|
||||
self.view.spinBoxPeriod.editingFinished.connect(self.setPeriod)
|
||||
self.view.comboBoxPeriod.currentIndexChanged.connect(self.setPeriod)
|
||||
self.view.spinBoxTriggers.editingFinished.connect(self.setTriggers)
|
||||
|
||||
def refresh(self):
|
||||
self.getReadout()
|
||||
self.getRunFrequency()
|
||||
self.getTransceiver()
|
||||
self.getAnalog()
|
||||
self.getDigital()
|
||||
self.getADCFrequency()
|
||||
self.getADCPhase()
|
||||
self.getADCPipeline()
|
||||
self.getDBITFrequency()
|
||||
self.getDBITPhase()
|
||||
self.getDBITPipeline()
|
||||
self.getFileWrite()
|
||||
self.getFileName()
|
||||
self.getFilePath()
|
||||
self.getAccquisitionIndex()
|
||||
self.getFrames()
|
||||
self.getTriggers()
|
||||
self.getPeriod()
|
||||
self.updateDetectorStatus(self.det.status)
|
||||
|
||||
# Acquisition Tab functions
|
||||
|
||||
def getReadout(self):
|
||||
self.view.comboBoxROMode.currentIndexChanged.disconnect()
|
||||
self.view.spinBoxAnalog.editingFinished.disconnect()
|
||||
self.view.spinBoxDigital.editingFinished.disconnect()
|
||||
self.view.spinBoxTransceiver.editingFinished.disconnect()
|
||||
|
||||
self.mainWindow.romode = self.det.romode
|
||||
self.view.comboBoxROMode.setCurrentIndex(self.mainWindow.romode.value)
|
||||
match self.mainWindow.romode:
|
||||
case readoutMode.ANALOG_ONLY:
|
||||
self.view.spinBoxAnalog.setEnabled(True)
|
||||
self.view.labelAnalog.setEnabled(True)
|
||||
self.view.spinBoxDigital.setDisabled(True)
|
||||
self.view.labelDigital.setDisabled(True)
|
||||
self.view.labelTransceiver.setDisabled(True)
|
||||
self.view.spinBoxTransceiver.setDisabled(True)
|
||||
case readoutMode.DIGITAL_ONLY:
|
||||
self.view.spinBoxAnalog.setDisabled(True)
|
||||
self.view.labelAnalog.setDisabled(True)
|
||||
self.view.spinBoxDigital.setEnabled(True)
|
||||
self.view.labelDigital.setEnabled(True)
|
||||
self.view.labelTransceiver.setDisabled(True)
|
||||
self.view.spinBoxTransceiver.setDisabled(True)
|
||||
case readoutMode.ANALOG_AND_DIGITAL:
|
||||
self.view.spinBoxAnalog.setEnabled(True)
|
||||
self.view.labelAnalog.setEnabled(True)
|
||||
self.view.spinBoxDigital.setEnabled(True)
|
||||
self.view.labelDigital.setEnabled(True)
|
||||
self.view.labelTransceiver.setDisabled(True)
|
||||
self.view.spinBoxTransceiver.setDisabled(True)
|
||||
case readoutMode.TRANSCEIVER_ONLY:
|
||||
self.view.spinBoxAnalog.setDisabled(True)
|
||||
self.view.labelAnalog.setDisabled(True)
|
||||
self.view.spinBoxDigital.setDisabled(True)
|
||||
self.view.labelDigital.setDisabled(True)
|
||||
self.view.labelTransceiver.setEnabled(True)
|
||||
self.view.spinBoxTransceiver.setEnabled(True)
|
||||
case _:
|
||||
self.view.spinBoxAnalog.setDisabled(True)
|
||||
self.view.labelAnalog.setDisabled(True)
|
||||
self.view.spinBoxDigital.setEnabled(True)
|
||||
self.view.labelDigital.setEnabled(True)
|
||||
self.view.labelTransceiver.setEnabled(True)
|
||||
self.view.spinBoxTransceiver.setEnabled(True)
|
||||
|
||||
self.view.comboBoxROMode.currentIndexChanged.connect(self.setReadOut)
|
||||
self.view.spinBoxAnalog.editingFinished.connect(self.setAnalog)
|
||||
self.view.spinBoxDigital.editingFinished.connect(self.setDigital)
|
||||
self.view.spinBoxTransceiver.editingFinished.connect(self.setTransceiver)
|
||||
self.getAnalog()
|
||||
self.getDigital()
|
||||
self.plotTab.showPlot()
|
||||
|
||||
def plotReferesh(self):
|
||||
self.read_zmq()
|
||||
|
||||
def setReadOut(self):
|
||||
self.view.comboBoxROMode.currentIndexChanged.disconnect()
|
||||
try:
|
||||
if self.view.comboBoxROMode.currentIndex() == 0:
|
||||
self.det.romode = readoutMode.ANALOG_ONLY
|
||||
elif self.view.comboBoxROMode.currentIndex() == 1:
|
||||
self.det.romode = readoutMode.DIGITAL_ONLY
|
||||
elif self.view.comboBoxROMode.currentIndex() == 2:
|
||||
self.det.romode = readoutMode.ANALOG_AND_DIGITAL
|
||||
elif self.view.comboBoxROMode.currentIndex() == 3:
|
||||
self.det.romode = readoutMode.TRANSCEIVER_ONLY
|
||||
else:
|
||||
self.det.romode = readoutMode.DIGITAL_AND_TRANSCEIVER
|
||||
except Exception as e:
|
||||
QtWidgets.QMessageBox.warning(self.mainWindow, "Readout Mode Fail", str(e), QtWidgets.QMessageBox.Ok)
|
||||
# TODO: handling double event exceptions
|
||||
self.view.comboBoxROMode.currentIndexChanged.connect(self.setReadOut)
|
||||
self.getReadout()
|
||||
|
||||
def getRunFrequency(self):
|
||||
self.view.spinBoxRunF.editingFinished.disconnect()
|
||||
self.view.spinBoxRunF.setValue(self.det.runclk)
|
||||
self.view.spinBoxRunF.editingFinished.connect(self.setRunFrequency)
|
||||
|
||||
def setRunFrequency(self):
|
||||
self.view.spinBoxRunF.editingFinished.disconnect()
|
||||
try:
|
||||
self.det.runclk = self.view.spinBoxRunF.value()
|
||||
except Exception as e:
|
||||
QtWidgets.QMessageBox.warning(self.mainWindow, "Run Frequency Fail", str(e), QtWidgets.QMessageBox.Ok)
|
||||
# TODO: handling double event exceptions
|
||||
self.view.spinBoxRunF.editingFinished.connect(self.setRunFrequency)
|
||||
self.getRunFrequency()
|
||||
|
||||
def getTransceiver(self):
|
||||
self.view.spinBoxTransceiver.editingFinished.disconnect()
|
||||
self.tsamples = self.det.tsamples
|
||||
self.view.spinBoxTransceiver.setValue(self.tsamples)
|
||||
self.view.spinBoxTransceiver.editingFinished.connect(self.setTransceiver)
|
||||
|
||||
def setTransceiver(self):
|
||||
self.view.spinBoxTransceiver.editingFinished.disconnect()
|
||||
try:
|
||||
self.det.tsamples = self.view.spinBoxTransceiver.value()
|
||||
except Exception as e:
|
||||
QtWidgets.QMessageBox.warning(self.mainWindow, "Transceiver Samples Fail", str(e),
|
||||
QtWidgets.QMessageBox.Ok)
|
||||
# TODO: handling double event exceptions
|
||||
self.view.spinBoxTransceiver.editingFinished.connect(self.setTransceiver)
|
||||
self.getTransceiver()
|
||||
|
||||
def getAnalog(self):
|
||||
self.view.spinBoxAnalog.editingFinished.disconnect()
|
||||
self.asamples = self.det.asamples
|
||||
self.view.spinBoxAnalog.setValue(self.asamples)
|
||||
self.view.spinBoxAnalog.editingFinished.connect(self.setAnalog)
|
||||
|
||||
def setAnalog(self):
|
||||
self.view.spinBoxAnalog.editingFinished.disconnect()
|
||||
try:
|
||||
self.det.asamples = self.view.spinBoxAnalog.value()
|
||||
except Exception as e:
|
||||
QtWidgets.QMessageBox.warning(self.mainWindow, "Digital Samples Fail", str(e), QtWidgets.QMessageBox.Ok)
|
||||
# TODO: handling double event exceptions
|
||||
self.view.spinBoxAnalog.editingFinished.connect(self.setAnalog)
|
||||
self.getAnalog()
|
||||
|
||||
def getDigital(self):
|
||||
self.view.spinBoxDigital.editingFinished.disconnect()
|
||||
self.dsamples = self.det.dsamples
|
||||
self.view.spinBoxDigital.setValue(self.dsamples)
|
||||
self.view.spinBoxDigital.editingFinished.connect(self.setDigital)
|
||||
|
||||
def setDigital(self):
|
||||
self.view.spinBoxDigital.editingFinished.disconnect()
|
||||
try:
|
||||
self.det.dsamples = self.view.spinBoxDigital.value()
|
||||
except Exception as e:
|
||||
QtWidgets.QMessageBox.warning(self.mainWindow, "Digital Samples Fail", str(e), QtWidgets.QMessageBox.Ok)
|
||||
# TODO: handling double event exceptions
|
||||
self.view.spinBoxDigital.editingFinished.connect(self.setDigital)
|
||||
self.getDigital()
|
||||
|
||||
def getADCFrequency(self):
|
||||
self.view.spinBoxADCF.editingFinished.disconnect()
|
||||
self.view.spinBoxADCF.setValue(self.det.adcclk)
|
||||
self.view.spinBoxADCF.editingFinished.connect(self.setADCFrequency)
|
||||
|
||||
def setADCFrequency(self):
|
||||
self.view.spinBoxADCF.editingFinished.disconnect()
|
||||
try:
|
||||
self.det.adcclk = self.view.spinBoxADCF.value()
|
||||
except Exception as e:
|
||||
QtWidgets.QMessageBox.warning(self.mainWindow, "ADC Frequency Fail", str(e), QtWidgets.QMessageBox.Ok)
|
||||
# TODO: handling double event exceptions
|
||||
self.view.spinBoxADCF.editingFinished.connect(self.setADCFrequency)
|
||||
self.getADCFrequency()
|
||||
|
||||
def getADCPhase(self):
|
||||
self.view.spinBoxADCPhase.editingFinished.disconnect()
|
||||
self.view.spinBoxADCPhase.setValue(self.det.adcphase)
|
||||
self.view.spinBoxADCPhase.editingFinished.connect(self.setADCPhase)
|
||||
|
||||
def setADCPhase(self):
|
||||
self.view.spinBoxADCPhase.editingFinished.disconnect()
|
||||
try:
|
||||
self.det.adcphase = self.view.spinBoxADCPhase.value()
|
||||
except Exception as e:
|
||||
QtWidgets.QMessageBox.warning(self.mainWindow, "ADC Phase Fail", str(e), QtWidgets.QMessageBox.Ok)
|
||||
# TODO: handling double event exceptions
|
||||
self.view.spinBoxADCPhase.editingFinished.connect(self.setADCPhase)
|
||||
self.getADCPhase()
|
||||
|
||||
def getADCPipeline(self):
|
||||
self.view.spinBoxADCPipeline.editingFinished.disconnect()
|
||||
self.view.spinBoxADCPipeline.setValue(self.det.adcpipeline)
|
||||
self.view.spinBoxADCPipeline.editingFinished.connect(self.setADCPipeline)
|
||||
|
||||
def setADCPipeline(self):
|
||||
self.view.spinBoxADCPipeline.editingFinished.disconnect()
|
||||
try:
|
||||
self.det.adcpipeline = self.view.spinBoxADCPipeline.value()
|
||||
except Exception as e:
|
||||
QtWidgets.QMessageBox.warning(self.mainWindow, "ADC Pipeline Fail", str(e), QtWidgets.QMessageBox.Ok)
|
||||
# TODO: handling double event exceptions
|
||||
self.view.spinBoxADCPipeline.editingFinished.connect(self.setADCPipeline)
|
||||
self.getADCPipeline()
|
||||
|
||||
def getDBITFrequency(self):
|
||||
self.view.spinBoxDBITF.editingFinished.disconnect()
|
||||
self.view.spinBoxDBITF.setValue(self.det.dbitclk)
|
||||
self.view.spinBoxDBITF.editingFinished.connect(self.setDBITFrequency)
|
||||
|
||||
def setDBITFrequency(self):
|
||||
self.view.spinBoxDBITF.editingFinished.disconnect()
|
||||
try:
|
||||
self.det.dbitclk = self.view.spinBoxDBITF.value()
|
||||
except Exception as e:
|
||||
QtWidgets.QMessageBox.warning(self.mainWindow, "DBit Frequency Fail", str(e), QtWidgets.QMessageBox.Ok)
|
||||
# TODO: handling double event exceptions
|
||||
self.view.spinBoxDBITF.editingFinished.connect(self.setDBITFrequency)
|
||||
self.getDBITFrequency()
|
||||
|
||||
def getDBITPhase(self):
|
||||
self.view.spinBoxDBITPhase.editingFinished.disconnect()
|
||||
self.view.spinBoxDBITPhase.setValue(self.det.dbitphase)
|
||||
self.view.spinBoxDBITPhase.editingFinished.connect(self.setDBITPhase)
|
||||
|
||||
def setDBITPhase(self):
|
||||
self.view.spinBoxDBITPhase.editingFinished.disconnect()
|
||||
try:
|
||||
self.det.dbitphase = self.view.spinBoxDBITPhase.value()
|
||||
except Exception as e:
|
||||
QtWidgets.QMessageBox.warning(self.mainWindow, "DBit Phase Fail", str(e), QtWidgets.QMessageBox.Ok)
|
||||
# TODO: handling double event exceptions
|
||||
self.view.spinBoxDBITPhase.editingFinished.connect(self.setDBITPhase)
|
||||
self.getDBITPhase()
|
||||
|
||||
def getDBITPipeline(self):
|
||||
self.view.spinBoxDBITPipeline.editingFinished.disconnect()
|
||||
self.view.spinBoxDBITPipeline.setValue(self.det.dbitpipeline)
|
||||
self.view.spinBoxDBITPipeline.editingFinished.connect(self.setDBITPipeline)
|
||||
|
||||
def setDBITPipeline(self):
|
||||
self.view.spinBoxDBITPipeline.editingFinished.disconnect()
|
||||
try:
|
||||
self.det.dbitpipeline = self.view.spinBoxDBITPipeline.value()
|
||||
except Exception as e:
|
||||
QtWidgets.QMessageBox.warning(self.mainWindow, "DBit Pipeline Fail", str(e), QtWidgets.QMessageBox.Ok)
|
||||
# TODO: handling double event exceptions
|
||||
self.view.spinBoxDBITPipeline.editingFinished.connect(self.setDBITPipeline)
|
||||
self.getDBITPipeline()
|
||||
|
||||
def getFileWrite(self):
|
||||
self.view.checkBoxFileWriteRaw.stateChanged.disconnect()
|
||||
self.view.checkBoxFileWriteRaw.setChecked(self.det.fwrite)
|
||||
self.view.checkBoxFileWriteRaw.stateChanged.connect(self.setFileWrite)
|
||||
|
||||
def setFileWrite(self):
|
||||
self.det.fwrite = self.view.checkBoxFileWriteRaw.isChecked()
|
||||
self.getFileWrite()
|
||||
|
||||
def setFileWriteNumpy(self):
|
||||
"""
|
||||
slot for saving the data in numpy (.npy) format
|
||||
"""
|
||||
self.writeNumpy = not self.writeNumpy
|
||||
|
||||
def getFileName(self):
|
||||
"""
|
||||
set the lineEditFilePath input widget to the filename value from the detector
|
||||
"""
|
||||
|
||||
self.view.lineEditFileName.editingFinished.disconnect()
|
||||
fileName = self.det.fname
|
||||
self.view.lineEditFileName.setText(fileName)
|
||||
self.outputFileNamePrefix = fileName
|
||||
self.view.lineEditFileName.editingFinished.connect(self.setFileName)
|
||||
|
||||
def setFileName(self):
|
||||
"""
|
||||
slot for setting the filename from the widget to the detector
|
||||
"""
|
||||
self.det.fname = self.view.lineEditFileName.text()
|
||||
self.getFileName()
|
||||
|
||||
def getFilePath(self):
|
||||
"""
|
||||
set the lineEditFilePath input widget to the path value from the detector
|
||||
"""
|
||||
self.view.lineEditFilePath.editingFinished.disconnect()
|
||||
path = self.det.fpath
|
||||
self.view.lineEditFilePath.setText(str(path))
|
||||
self.view.lineEditFilePath.editingFinished.connect(self.setFilePath)
|
||||
self.outputDir = path
|
||||
|
||||
def setFilePath(self):
|
||||
"""
|
||||
slot to set the directory of the output for the detector
|
||||
"""
|
||||
self.det.fpath = Path(self.view.lineEditFilePath.text())
|
||||
self.getFilePath()
|
||||
|
||||
def saveNumpyFile(self, data: np.ndarray | dict[str, np.ndarray], jsonHeader):
|
||||
"""
|
||||
save the acquisition data (waveform or image) in the specified path
|
||||
save waveform in multiple .npy files
|
||||
save image as npy file format
|
||||
@note: frame number can be up to 100,000 so the data arrays cannot be fully loaded to memory
|
||||
"""
|
||||
if not self.writeNumpy:
|
||||
return
|
||||
if self.outputDir == Path('/'):
|
||||
self.outputDir = Path('./')
|
||||
if self.outputFileNamePrefix == '':
|
||||
self.outputFileNamePrefix = 'run'
|
||||
|
||||
for device in data:
|
||||
if device not in self.numpyFileManagers:
|
||||
tmpPath = self.outputDir / f'{self.outputFileNamePrefix}_{device}_{jsonHeader["fileIndex"]}.npy'
|
||||
self.numpyFileManagers[device] = NumpyFileManager(tmpPath, 'w', data[device].shape, data[device].dtype)
|
||||
self.numpyFileManagers[device].writeOneFrame(data[device])
|
||||
|
||||
if 'progress' in jsonHeader and jsonHeader['progress'] >= 100:
|
||||
# close opened files after saving the last frame
|
||||
self.closeOpenedNumpyFiles(jsonHeader)
|
||||
|
||||
def closeOpenedNumpyFiles(self, jsonHeader):
|
||||
"""
|
||||
create npz file for waveform plots and close opened numpy files to persist their data
|
||||
"""
|
||||
if not self.writeNumpy:
|
||||
return
|
||||
if len(self.numpyFileManagers) == 0:
|
||||
return
|
||||
oneFile: bool = len(self.numpyFileManagers) == 1
|
||||
|
||||
filepaths = [npw.file.name for device, npw in self.numpyFileManagers.items()]
|
||||
filenames = list(self.numpyFileManagers.keys())
|
||||
ext = 'npy' if oneFile else 'npz'
|
||||
newPath = self.outputDir / f'{self.outputFileNamePrefix}_{jsonHeader["fileIndex"]}.{ext}'
|
||||
|
||||
if not oneFile:
|
||||
# if there is multiple .npy files group them in an .npz file
|
||||
self.numpyFileManagers.clear()
|
||||
NpzFileWriter.zipNpyFiles(newPath, filepaths, filenames, deleteOriginals=True, compressed=False)
|
||||
else:
|
||||
# rename files from "run_ADC0_0.npy" to "run_0.npy" if it is a single .npy file
|
||||
oldPath = self.outputDir / f'{self.outputFileNamePrefix}_' \
|
||||
f'{self.numpyFileManagers.popitem()[0]}_{jsonHeader["fileIndex"]}.{ext}'
|
||||
Path.rename(oldPath, newPath)
|
||||
|
||||
self.logger.info(f'Saving numpy data in {newPath} Finished')
|
||||
|
||||
def browseFilePath(self):
|
||||
response = QtWidgets.QFileDialog.getExistingDirectory(parent=self.mainWindow,
|
||||
caption="Select Path to Save Output File",
|
||||
directory=str(Path.cwd()),
|
||||
options=(QtWidgets.QFileDialog.ShowDirsOnly
|
||||
| QtWidgets.QFileDialog.DontResolveSymlinks)
|
||||
# filter='README (*.md *.ui)'
|
||||
)
|
||||
if response:
|
||||
self.view.lineEditFilePath.setText(response)
|
||||
self.setFilePath()
|
||||
|
||||
def getAccquisitionIndex(self):
|
||||
self.view.spinBoxAcquisitionIndex.editingFinished.disconnect()
|
||||
self.view.spinBoxAcquisitionIndex.setValue(self.det.findex)
|
||||
self.view.spinBoxAcquisitionIndex.editingFinished.connect(self.setAccquisitionIndex)
|
||||
|
||||
def setAccquisitionIndex(self):
|
||||
self.det.findex = self.view.spinBoxAcquisitionIndex.value()
|
||||
self.getAccquisitionIndex()
|
||||
|
||||
def getFrames(self):
|
||||
self.view.spinBoxFrames.editingFinished.disconnect()
|
||||
self.view.spinBoxFrames.setValue(self.det.frames)
|
||||
self.view.spinBoxFrames.editingFinished.connect(self.setFrames)
|
||||
|
||||
def setFrames(self):
|
||||
self.det.frames = self.view.spinBoxFrames.value()
|
||||
self.getFrames()
|
||||
|
||||
def getPeriod(self):
|
||||
self.view.spinBoxPeriod.editingFinished.disconnect()
|
||||
self.view.comboBoxPeriod.currentIndexChanged.disconnect()
|
||||
|
||||
# Converting to right time unit for period
|
||||
tPeriod = self.det.period
|
||||
if tPeriod < 100e-9:
|
||||
self.view.comboBoxPeriod.setCurrentIndex(3)
|
||||
self.view.spinBoxPeriod.setValue(tPeriod / 1e-9)
|
||||
elif tPeriod < 100e-6:
|
||||
self.view.comboBoxPeriod.setCurrentIndex(2)
|
||||
self.view.spinBoxPeriod.setValue(tPeriod / 1e-6)
|
||||
elif tPeriod < 100e-3:
|
||||
self.view.comboBoxPeriod.setCurrentIndex(1)
|
||||
self.view.spinBoxPeriod.setValue(tPeriod / 1e-3)
|
||||
else:
|
||||
self.view.comboBoxPeriod.setCurrentIndex(0)
|
||||
self.view.spinBoxPeriod.setValue(tPeriod)
|
||||
|
||||
self.view.spinBoxPeriod.editingFinished.connect(self.setPeriod)
|
||||
self.view.comboBoxPeriod.currentIndexChanged.connect(self.setPeriod)
|
||||
|
||||
def setPeriod(self):
|
||||
if self.view.comboBoxPeriod.currentIndex() == 0:
|
||||
self.det.period = self.view.spinBoxPeriod.value()
|
||||
elif self.view.comboBoxPeriod.currentIndex() == 1:
|
||||
self.det.period = self.view.spinBoxPeriod.value() * (1e-3)
|
||||
elif self.view.comboBoxPeriod.currentIndex() == 2:
|
||||
self.det.period = self.view.spinBoxPeriod.value() * (1e-6)
|
||||
else:
|
||||
self.det.period = self.view.spinBoxPeriod.value() * (1e-9)
|
||||
|
||||
self.getPeriod()
|
||||
|
||||
def getTriggers(self):
|
||||
self.view.spinBoxTriggers.editingFinished.disconnect()
|
||||
self.view.spinBoxTriggers.setValue(self.det.triggers)
|
||||
self.view.spinBoxTriggers.editingFinished.connect(self.setTriggers)
|
||||
|
||||
def setTriggers(self):
|
||||
self.det.triggers = self.view.spinBoxTriggers.value()
|
||||
self.getTriggers()
|
||||
|
||||
def updateDetectorStatus(self, status):
|
||||
self.mainWindow.labelDetectorStatus.setText(status.name)
|
||||
|
||||
def updateCurrentMeasurement(self):
|
||||
self.mainWindow.labelCurrentMeasurement.setText(str(self.currentMeasurement))
|
||||
# print(f"Meausrement {self.currentMeasurement}")
|
||||
|
||||
def updateCurrentFrame(self, val):
|
||||
self.mainWindow.labelFrameNumber.setText(str(val))
|
||||
|
||||
def updateAcquiredFrames(self, val):
|
||||
self.mainWindow.labelAcquiredFrames.setText(str(val))
|
||||
|
||||
def toggleAcquire(self):
|
||||
if self.mainWindow.pushButtonStart.isChecked():
|
||||
self.plotTab.showPatternViewer(False)
|
||||
self.acquire()
|
||||
else:
|
||||
self.stopAcquisition()
|
||||
|
||||
def toggleStartButton(self, started):
|
||||
if started:
|
||||
self.mainWindow.pushButtonStart.setChecked(True)
|
||||
self.mainWindow.pushButtonStart.setText('Stop')
|
||||
else:
|
||||
self.mainWindow.pushButtonStart.setChecked(False)
|
||||
self.mainWindow.pushButtonStart.setText('Start')
|
||||
|
||||
def stopAcquisition(self):
|
||||
self.det.stop()
|
||||
self.stoppedFlag = True
|
||||
|
||||
def checkBeforeAcquire(self):
|
||||
if self.plotTab.view.radioButtonImage.isChecked():
|
||||
# matterhorn image
|
||||
if self.plotTab.view.comboBoxPlot.currentText() == "Matterhorn":
|
||||
if self.mainWindow.romode not in [readoutMode.TRANSCEIVER_ONLY, readoutMode.DIGITAL_AND_TRANSCEIVER]:
|
||||
QtWidgets.QMessageBox.warning(self.mainWindow, "Plot type",
|
||||
"To read Matterhorn image, please enable transceiver readout mode",
|
||||
QtWidgets.QMessageBox.Ok)
|
||||
return False
|
||||
if self.transceiverTab.getTransceiverEnableReg() != Defines.Matterhorn.tranceiverEnable:
|
||||
QtWidgets.QMessageBox.warning(
|
||||
self.mainWindow, "Plot type", "To read Matterhorn image, please set transceiver enable to " +
|
||||
str(Defines.Matterhorn.tranceiverEnable), QtWidgets.QMessageBox.Ok)
|
||||
return False
|
||||
# moench04 image
|
||||
elif self.plotTab.view.comboBoxPlot.currentText() == "Moench04":
|
||||
if self.mainWindow.romode not in [readoutMode.ANALOG_ONLY, readoutMode.ANALOG_AND_DIGITAL]:
|
||||
QtWidgets.QMessageBox.warning(self.mainWindow, "Plot type",
|
||||
"To read Moench 04 image, please enable analog readout mode",
|
||||
QtWidgets.QMessageBox.Ok)
|
||||
return False
|
||||
if self.mainWindow.nADCEnabled != 32:
|
||||
QtWidgets.QMessageBox.warning(self.mainWindow, "Plot type",
|
||||
"To read Moench 04 image, please enable all 32 adcs",
|
||||
QtWidgets.QMessageBox.Ok)
|
||||
return False
|
||||
return True
|
||||
|
||||
def acquire(self):
|
||||
if not self.checkBeforeAcquire():
|
||||
self.toggleStartButton(False)
|
||||
return
|
||||
|
||||
self.stoppedFlag = False
|
||||
self.toggleStartButton(True)
|
||||
self.currentMeasurement = 0
|
||||
|
||||
# ensure zmq streaming is enabled
|
||||
if self.det.rx_zmqstream == 0:
|
||||
self.det.rx_zmqstream = 1
|
||||
|
||||
# some functions that must be updated for local values
|
||||
self.getTransceiver()
|
||||
self.getAnalog()
|
||||
self.getDigital()
|
||||
self.getReadout()
|
||||
self.signalsTab.getDBitOffset()
|
||||
self.adcTab.getADCEnableReg()
|
||||
self.signalsTab.updateDigitalBitEnable()
|
||||
self.transceiverTab.getTransceiverEnableReg()
|
||||
|
||||
self.startMeasurement()
|
||||
|
||||
def startMeasurement(self):
|
||||
try:
|
||||
self.updateCurrentMeasurement()
|
||||
self.updateCurrentFrame(0)
|
||||
self.updateAcquiredFrames(0)
|
||||
self.mainWindow.progressBar.setValue(0)
|
||||
|
||||
self.det.rx_start()
|
||||
self.det.start()
|
||||
time.sleep(Defines.Time_Wait_For_Packets_ms)
|
||||
self.checkEndofAcquisition()
|
||||
except Exception as e:
|
||||
QtWidgets.QMessageBox.warning(self.mainWindow, "Acquire Fail", str(e), QtWidgets.QMessageBox.Ok)
|
||||
self.checkEndofAcquisition()
|
||||
|
||||
def checkEndofAcquisition(self):
|
||||
caught = self.det.rx_framescaught[0]
|
||||
self.updateAcquiredFrames(caught)
|
||||
status = self.det.getDetectorStatus()[0]
|
||||
self.updateDetectorStatus(status)
|
||||
measurementDone = False
|
||||
# print(f'status:{status}')
|
||||
match status:
|
||||
case runStatus.RUNNING:
|
||||
pass
|
||||
case runStatus.WAITING:
|
||||
pass
|
||||
case runStatus.TRANSMITTING:
|
||||
pass
|
||||
case _:
|
||||
measurementDone = True
|
||||
|
||||
# check for 500ms for no packets
|
||||
# needs more time for 1g streaming out done
|
||||
if measurementDone:
|
||||
time.sleep(Defines.Time_Wait_For_Packets_ms)
|
||||
if self.det.rx_framescaught[0] != caught:
|
||||
measurementDone = False
|
||||
|
||||
numMeasurments = self.view.spinBoxMeasurements.value()
|
||||
if measurementDone:
|
||||
|
||||
if self.det.rx_status == runStatus.RUNNING:
|
||||
self.det.rx_stop()
|
||||
if self.view.checkBoxFileWriteRaw.isChecked() or self.view.checkBoxFileWriteNumpy.isChecked():
|
||||
self.view.spinBoxAcquisitionIndex.stepUp()
|
||||
self.setAccquisitionIndex()
|
||||
# next measurement
|
||||
self.currentMeasurement += 1
|
||||
if self.currentMeasurement < numMeasurments and not self.stoppedFlag:
|
||||
self.startMeasurement()
|
||||
else:
|
||||
self.mainWindow.statusTimer.stop()
|
||||
self.toggleStartButton(False)
|
||||
else:
|
||||
self.mainWindow.statusTimer.start(Defines.Time_Status_Refresh_ms)
|
||||
|
||||
# For other functios
|
||||
# Reading data from zmq and decoding it
|
||||
def read_zmq(self):
|
||||
# print("in readzmq")
|
||||
try:
|
||||
msg = self.socket.recv_multipart(flags=zmq.NOBLOCK)
|
||||
if len(msg) != 2:
|
||||
if len(msg) != 1:
|
||||
print(f'len(msg) = {len(msg)}')
|
||||
return
|
||||
header, data = msg
|
||||
jsonHeader = json.loads(header)
|
||||
self.mainWindow.progressBar.setValue(int(jsonHeader['progress']))
|
||||
self.updateCurrentFrame(jsonHeader['frameIndex'])
|
||||
|
||||
# waveform
|
||||
waveforms = {}
|
||||
if self.plotTab.view.radioButtonWaveform.isChecked():
|
||||
# analog
|
||||
if self.mainWindow.romode.value in [0, 2]:
|
||||
waveforms |= self.adcTab.processWaveformData(data, self.asamples)
|
||||
# digital
|
||||
if self.mainWindow.romode.value in [1, 2, 4]:
|
||||
waveforms |= self.signalsTab.processWaveformData(data, self.asamples, self.dsamples)
|
||||
# transceiver
|
||||
if self.mainWindow.romode.value in [3, 4]:
|
||||
waveforms |= self.transceiverTab.processWaveformData(data, self.dsamples)
|
||||
# image
|
||||
else:
|
||||
# analog
|
||||
if self.mainWindow.romode.value in [0, 2]:
|
||||
waveforms['analog_image'] = self.adcTab.processImageData(data, self.asamples)
|
||||
# transceiver
|
||||
if self.mainWindow.romode.value in [3, 4]:
|
||||
waveforms['tx_image'] = self.transceiverTab.processImageData(data, self.dsamples)
|
||||
|
||||
self.saveNumpyFile(waveforms, jsonHeader)
|
||||
except zmq.ZMQError:
|
||||
pass
|
||||
except Exception:
|
||||
self.logger.exception("Exception caught")
|
||||
|
||||
def setup_zmq(self):
|
||||
self.det.rx_zmqstream = 1
|
||||
self.zmqIp = self.det.rx_zmqip
|
||||
self.zmqport = self.det.rx_zmqport
|
||||
self.zmq_stream = self.det.rx_zmqstream
|
||||
|
||||
self.context = zmq.Context()
|
||||
self.socket = self.context.socket(zmq.SUB)
|
||||
self.socket.connect(f"tcp://{self.zmqIp}:{self.zmqport}")
|
||||
self.socket.subscribe("")
|
||||
|
||||
def saveParameters(self) -> list[str]:
|
||||
return [
|
||||
f'romode {self.view.comboBoxROMode.currentText().lower()}',
|
||||
f'runclk {self.view.spinBoxRunF.value()}',
|
||||
f'adcclk {self.view.spinBoxADCF.value()}',
|
||||
f'adcphase {self.view.spinBoxADCPhase.value()}',
|
||||
f'adcpipeline {self.view.spinBoxADCPipeline.value()}',
|
||||
f'dbitclk {self.view.spinBoxDBITF.value()}',
|
||||
f'dbitphase {self.view.spinBoxDBITPhase.value()}',
|
||||
f'dbitpipeline {self.view.spinBoxDBITPipeline.value()}',
|
||||
f'fwrite {int(self.view.checkBoxFileWriteRaw.isChecked())}',
|
||||
f'fname {self.view.lineEditFileName.text()}',
|
||||
f'fpath {self.view.lineEditFilePath.text()}',
|
||||
f'findex {self.view.spinBoxAcquisitionIndex.value()}',
|
||||
f'frames {self.view.spinBoxFrames.value()}',
|
||||
f'triggers {self.view.spinBoxTriggers.value()}',
|
||||
f'period {self.view.spinBoxPeriod.value()} {self.view.comboBoxPeriod.currentText().lower()}',
|
||||
f'asamples {self.view.spinBoxAnalog.value()}',
|
||||
f'dsamples {self.view.spinBoxDigital.value()}',
|
||||
f'tsamples {self.view.spinBoxTransceiver.value()}',
|
||||
]
|
||||
170
pyctbgui/pyctbgui/services/DACs.py
Normal file
170
pyctbgui/pyctbgui/services/DACs.py
Normal file
@@ -0,0 +1,170 @@
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt5 import QtWidgets, uic
|
||||
from pyctbgui.utils.defines import Defines
|
||||
|
||||
from slsdet import dacIndex
|
||||
|
||||
|
||||
class DacTab(QtWidgets.QWidget):
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
uic.loadUi(Path(__file__).parent.parent / 'ui' / "Dacs.ui", parent)
|
||||
self.view = parent
|
||||
|
||||
def setup_ui(self):
|
||||
for i in range(Defines.dac.count):
|
||||
dac = getattr(dacIndex, f"DAC_{i}")
|
||||
getattr(self.view, f"spinBoxDAC{i}").setValue(self.det.getDAC(dac)[0])
|
||||
|
||||
if self.det.highvoltage == 0:
|
||||
self.view.spinBoxHighVoltage.setDisabled(True)
|
||||
self.view.checkBoxHighVoltage.setChecked(False)
|
||||
|
||||
def connect_ui(self):
|
||||
n_dacs = len(self.det.daclist)
|
||||
for i in range(n_dacs):
|
||||
getattr(self.view, f"spinBoxDAC{i}").editingFinished.connect(partial(self.setDAC, i))
|
||||
getattr(self.view, f"checkBoxDAC{i}").stateChanged.connect(partial(self.setDACTristate, i))
|
||||
getattr(self.view, f"checkBoxDAC{i}mV").stateChanged.connect(partial(self.getDAC, i))
|
||||
|
||||
self.view.comboBoxADCVpp.currentIndexChanged.connect(self.setADCVpp)
|
||||
self.view.spinBoxHighVoltage.editingFinished.connect(self.setHighVoltage)
|
||||
self.view.checkBoxHighVoltage.stateChanged.connect(self.setHighVoltage)
|
||||
|
||||
def refresh(self):
|
||||
self.updateDACNames()
|
||||
for i in range(Defines.dac.count):
|
||||
self.getDACTristate(i)
|
||||
self.getDAC(i)
|
||||
|
||||
self.getADCVpp()
|
||||
self.getHighVoltage()
|
||||
|
||||
def updateDACNames(self):
|
||||
for i, name in enumerate(self.det.getDacNames()):
|
||||
getattr(self.view, f"checkBoxDAC{i}").setText(name)
|
||||
|
||||
def getDACTristate(self, i):
|
||||
checkBox = getattr(self.view, f"checkBoxDAC{i}")
|
||||
dac = getattr(dacIndex, f"DAC_{i}")
|
||||
checkBox.stateChanged.disconnect()
|
||||
if (self.det.getDAC(dac)[0]) == -100:
|
||||
checkBox.setChecked(False)
|
||||
else:
|
||||
checkBox.setChecked(True)
|
||||
checkBox.stateChanged.connect(partial(self.setDACTristate, i))
|
||||
|
||||
def setDACTristate(self, i):
|
||||
checkBox = getattr(self.view, f"checkBoxDAC{i}")
|
||||
if not checkBox.isChecked():
|
||||
self.setDAC(i)
|
||||
self.getDAC(i)
|
||||
|
||||
def getDAC(self, i):
|
||||
checkBox = getattr(self.view, f"checkBoxDAC{i}")
|
||||
checkBoxmV = getattr(self.view, f"checkBoxDAC{i}mV")
|
||||
spinBox = getattr(self.view, f"spinBoxDAC{i}")
|
||||
label = getattr(self.view, f"labelDAC{i}")
|
||||
dac = getattr(dacIndex, f"DAC_{i}")
|
||||
|
||||
checkBox.stateChanged.disconnect()
|
||||
checkBoxmV.stateChanged.disconnect()
|
||||
spinBox.editingFinished.disconnect()
|
||||
|
||||
# do not uncheck automatically
|
||||
if self.det.getDAC(dac)[0] != -100:
|
||||
checkBox.setChecked(True)
|
||||
|
||||
if checkBox.isChecked():
|
||||
spinBox.setEnabled(True)
|
||||
checkBoxmV.setEnabled(True)
|
||||
else:
|
||||
spinBox.setDisabled(True)
|
||||
checkBoxmV.setDisabled(True)
|
||||
|
||||
in_mv = checkBoxmV.isChecked() and checkBox.isChecked()
|
||||
dacValue = self.det.getDAC(dac, in_mv)[0]
|
||||
unit = "mV" if in_mv else ""
|
||||
label.setText(f"{dacValue} {unit}")
|
||||
spinBox.setValue(dacValue)
|
||||
|
||||
checkBox.stateChanged.connect(partial(self.setDACTristate, i))
|
||||
checkBoxmV.stateChanged.connect(partial(self.getDAC, i))
|
||||
spinBox.editingFinished.connect(partial(self.setDAC, i))
|
||||
|
||||
def setDAC(self, i):
|
||||
checkBoxDac = getattr(self.view, f"checkBoxDAC{i}")
|
||||
checkBoxmV = getattr(self.view, f"checkBoxDAC{i}mV")
|
||||
spinBox = getattr(self.view, f"spinBoxDAC{i}")
|
||||
dac = getattr(dacIndex, f"DAC_{i}")
|
||||
|
||||
value = -100
|
||||
if checkBoxDac.isChecked():
|
||||
value = spinBox.value()
|
||||
in_mV = checkBoxDac.isChecked() and checkBoxmV.isChecked()
|
||||
self.det.setDAC(dac, value, in_mV)
|
||||
self.getDAC(i)
|
||||
|
||||
def getADCVpp(self):
|
||||
retval = self.det.adcvpp
|
||||
self.view.labelADCVpp.setText(f'Mode: {str(retval)}')
|
||||
|
||||
self.view.comboBoxADCVpp.currentIndexChanged.disconnect()
|
||||
self.view.comboBoxADCVpp.setCurrentIndex(retval)
|
||||
self.view.comboBoxADCVpp.currentIndexChanged.connect(self.setADCVpp)
|
||||
|
||||
def setADCVpp(self):
|
||||
self.det.adcvpp = self.view.comboBoxADCVpp.currentIndex()
|
||||
self.getADCVpp()
|
||||
|
||||
def getHighVoltage(self):
|
||||
retval = self.det.highvoltage
|
||||
self.view.labelHighVoltage.setText(str(retval))
|
||||
|
||||
self.view.spinBoxHighVoltage.editingFinished.disconnect()
|
||||
self.view.checkBoxHighVoltage.stateChanged.disconnect()
|
||||
|
||||
self.view.spinBoxHighVoltage.setValue(retval)
|
||||
if retval:
|
||||
self.view.checkBoxHighVoltage.setChecked(True)
|
||||
if self.view.checkBoxHighVoltage.isChecked():
|
||||
self.view.spinBoxHighVoltage.setEnabled(True)
|
||||
|
||||
self.view.spinBoxHighVoltage.editingFinished.connect(self.setHighVoltage)
|
||||
self.view.checkBoxHighVoltage.stateChanged.connect(self.setHighVoltage)
|
||||
|
||||
def setHighVoltage(self):
|
||||
value = 0
|
||||
if self.view.checkBoxHighVoltage.isChecked():
|
||||
value = self.view.spinBoxHighVoltage.value()
|
||||
try:
|
||||
self.det.highvoltage = value
|
||||
except Exception as e:
|
||||
QtWidgets.QMessageBox.warning(self.mainWindow, "High Voltage Fail", str(e), QtWidgets.QMessageBox.Ok)
|
||||
pass
|
||||
self.getHighVoltage()
|
||||
|
||||
def saveParameters(self) -> list:
|
||||
"""
|
||||
save parameters for the current tab
|
||||
@return: list of commands
|
||||
"""
|
||||
commands = []
|
||||
for i in range(Defines.dac.count):
|
||||
# if checkbox disabled put -100 in dac units
|
||||
enabled = getattr(self.view, f"checkBoxDAC{i}").isChecked()
|
||||
if not enabled:
|
||||
commands.append(f"dac {i} -100")
|
||||
# else put the value in dac or mV units
|
||||
else:
|
||||
value = getattr(self.view, f"spinBoxDAC{i}").value()
|
||||
inMV = getattr(self.view, f"checkBoxDAC{i}mV").isChecked()
|
||||
unit = " mV" if inMV else ""
|
||||
commands.append(f"dac {i} {value}{unit}")
|
||||
|
||||
commands.append(f"adcvpp {self.view.comboBoxADCVpp.currentText()} mV")
|
||||
commands.append(f"highvoltage {self.view.spinBoxHighVoltage.value()}")
|
||||
return commands
|
||||
456
pyctbgui/pyctbgui/services/Pattern.py
Normal file
456
pyctbgui/pyctbgui/services/Pattern.py
Normal file
@@ -0,0 +1,456 @@
|
||||
import os
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt5 import QtWidgets, uic
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
|
||||
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
|
||||
|
||||
from pyctbgui.utils.defines import Defines
|
||||
from pyctbgui.utils.plotPattern import PlotPattern
|
||||
|
||||
|
||||
class PatternTab(QtWidgets.QWidget):
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
uic.loadUi(Path(__file__).parent.parent / 'ui' / "pattern.ui", parent)
|
||||
self.view = parent
|
||||
self.mainWindow = None
|
||||
self.det = None
|
||||
self.plotTab = None
|
||||
|
||||
def setup_ui(self):
|
||||
# Pattern Tab
|
||||
self.plotTab = self.mainWindow.plotTab
|
||||
|
||||
for i in range(len(Defines.Colors)):
|
||||
self.view.comboBoxPatColor.addItem(Defines.Colors[i])
|
||||
self.view.comboBoxPatWaitColor.addItem(Defines.Colors[i])
|
||||
self.view.comboBoxPatLoopColor.addItem(Defines.Colors[i])
|
||||
for i in range(len(Defines.LineStyles)):
|
||||
self.view.comboBoxPatWaitLineStyle.addItem(Defines.LineStyles[i])
|
||||
self.view.comboBoxPatLoopLineStyle.addItem(Defines.LineStyles[i])
|
||||
self.updateDefaultPatViewerParameters()
|
||||
self.view.comboBoxPatColorSelect.setCurrentIndex(0)
|
||||
self.view.comboBoxPatWait.setCurrentIndex(0)
|
||||
self.view.comboBoxPatLoop.setCurrentIndex(0)
|
||||
self.view.spinBoxPatClockSpacing.setValue(self.clock_vertical_lines_spacing)
|
||||
self.view.checkBoxPatShowClockNumber.setChecked(self.show_clocks_number)
|
||||
self.view.doubleSpinBoxLineWidth.setValue(self.line_width)
|
||||
self.view.lineEditPatternFile.setText(self.det.patfname[0])
|
||||
# rest gets updated after connecting to slots
|
||||
# pattern viewer plot area
|
||||
self.figure, self.ax = plt.subplots()
|
||||
self.canvas = FigureCanvas(self.figure)
|
||||
self.toolbar = NavigationToolbar(self.canvas, self.view)
|
||||
self.mainWindow.gridLayoutPatternViewer.addWidget(self.toolbar)
|
||||
self.mainWindow.gridLayoutPatternViewer.addWidget(self.canvas)
|
||||
self.figure.clear()
|
||||
|
||||
def connect_ui(self):
|
||||
# For Pattern Tab
|
||||
self.view.lineEditStartAddress.editingFinished.connect(self.setPatLimitAddress)
|
||||
self.view.lineEditStopAddress.editingFinished.connect(self.setPatLimitAddress)
|
||||
for i in range(Defines.pattern.loops_count):
|
||||
getattr(self.view,
|
||||
f"lineEditLoop{i}Start").editingFinished.connect(partial(self.setPatLoopStartStopAddress, i))
|
||||
getattr(self.view,
|
||||
f"lineEditLoop{i}Stop").editingFinished.connect(partial(self.setPatLoopStartStopAddress, i))
|
||||
getattr(self.view, f"lineEditLoop{i}Wait").editingFinished.connect(partial(self.setPatLoopWaitAddress, i))
|
||||
getattr(self.view,
|
||||
f"spinBoxLoop{i}Repetition").editingFinished.connect(partial(self.setPatLoopRepetition, i))
|
||||
getattr(self.view, f"spinBoxLoop{i}WaitTime").editingFinished.connect(partial(self.setPatLoopWaitTime, i))
|
||||
self.view.pushButtonCompiler.clicked.connect(self.setCompiler)
|
||||
self.view.pushButtonUncompiled.clicked.connect(self.setUncompiledPatternFile)
|
||||
self.view.pushButtonPatternFile.clicked.connect(self.setPatternFile)
|
||||
self.view.pushButtonLoadPattern.clicked.connect(self.loadPattern)
|
||||
|
||||
self.view.comboBoxPatColorSelect.currentIndexChanged.connect(self.getPatViewerColors)
|
||||
self.view.comboBoxPatWait.currentIndexChanged.connect(self.getPatViewerWaitParameters)
|
||||
self.view.comboBoxPatLoop.currentIndexChanged.connect(self.getPatViewerLoopParameters)
|
||||
|
||||
self.view.comboBoxPatColor.currentIndexChanged.connect(self.updatePatViewerParameters)
|
||||
self.view.comboBoxPatWaitColor.currentIndexChanged.connect(self.updatePatViewerParameters)
|
||||
self.view.comboBoxPatLoopColor.currentIndexChanged.connect(self.updatePatViewerParameters)
|
||||
self.view.comboBoxPatWaitLineStyle.currentIndexChanged.connect(self.updatePatViewerParameters)
|
||||
self.view.comboBoxPatLoopLineStyle.currentIndexChanged.connect(self.updatePatViewerParameters)
|
||||
self.view.doubleSpinBoxWaitAlpha.editingFinished.connect(self.updatePatViewerParameters)
|
||||
self.view.doubleSpinBoxLoopAlpha.editingFinished.connect(self.updatePatViewerParameters)
|
||||
self.view.doubleSpinBoxWaitAlphaRect.editingFinished.connect(self.updatePatViewerParameters)
|
||||
self.view.doubleSpinBoxLoopAlphaRect.editingFinished.connect(self.updatePatViewerParameters)
|
||||
self.view.spinBoxPatClockSpacing.editingFinished.connect(self.updatePatViewerParameters)
|
||||
self.view.checkBoxPatShowClockNumber.stateChanged.connect(self.updatePatViewerParameters)
|
||||
self.view.doubleSpinBoxLineWidth.editingFinished.connect(self.updatePatViewerParameters)
|
||||
self.view.pushButtonViewPattern.clicked.connect(self.viewPattern)
|
||||
|
||||
def refresh(self):
|
||||
self.getPatLimitAddress()
|
||||
for i in range(Defines.pattern.loops_count):
|
||||
self.getPatLoopStartStopAddress(i)
|
||||
self.getPatLoopWaitAddress(i)
|
||||
self.getPatLoopRepetition(i)
|
||||
self.getPatLoopWaitTime(i)
|
||||
|
||||
# Pattern Tab functions
|
||||
|
||||
def getPatLimitAddress(self):
|
||||
retval = self.det.patlimits
|
||||
self.view.lineEditStartAddress.editingFinished.disconnect()
|
||||
self.view.lineEditStopAddress.editingFinished.disconnect()
|
||||
self.view.lineEditStartAddress.setText("0x{:04x}".format(retval[0]))
|
||||
self.view.lineEditStopAddress.setText("0x{:04x}".format(retval[1]))
|
||||
self.view.lineEditStartAddress.editingFinished.connect(self.setPatLimitAddress)
|
||||
self.view.lineEditStopAddress.editingFinished.connect(self.setPatLimitAddress)
|
||||
|
||||
def setPatLimitAddress(self):
|
||||
self.view.lineEditStartAddress.editingFinished.disconnect()
|
||||
self.view.lineEditStopAddress.editingFinished.disconnect()
|
||||
try:
|
||||
start = int(self.view.lineEditStartAddress.text(), 16)
|
||||
stop = int(self.view.lineEditStopAddress.text(), 16)
|
||||
self.det.patlimits = [start, stop]
|
||||
except Exception as e:
|
||||
QtWidgets.QMessageBox.warning(self.mainWindow, "Pattern Limit Address Fail", str(e),
|
||||
QtWidgets.QMessageBox.Ok)
|
||||
pass
|
||||
# TODO: handling double event exceptions
|
||||
self.view.lineEditStartAddress.editingFinished.connect(self.setPatLimitAddress)
|
||||
self.view.lineEditStopAddress.editingFinished.connect(self.setPatLimitAddress)
|
||||
self.getPatLimitAddress()
|
||||
|
||||
def getPatLoopStartStopAddress(self, level):
|
||||
retval = self.det.patloop[level]
|
||||
lineEditStart = getattr(self.view, f"lineEditLoop{level}Start")
|
||||
lineEditStop = getattr(self.view, f"lineEditLoop{level}Stop")
|
||||
lineEditStart.editingFinished.disconnect()
|
||||
lineEditStop.editingFinished.disconnect()
|
||||
lineEditStart.setText("0x{:04x}".format(retval[0]))
|
||||
lineEditStop.setText("0x{:04x}".format(retval[1]))
|
||||
lineEditStart.editingFinished.connect(partial(self.setPatLoopStartStopAddress, level))
|
||||
lineEditStop.editingFinished.connect(partial(self.setPatLoopStartStopAddress, level))
|
||||
|
||||
def setPatLoopStartStopAddress(self, level):
|
||||
lineEditStart = getattr(self.view, f"lineEditLoop{level}Start")
|
||||
lineEditStop = getattr(self.view, f"lineEditLoop{level}Stop")
|
||||
lineEditStart.editingFinished.disconnect()
|
||||
lineEditStop.editingFinished.disconnect()
|
||||
try:
|
||||
start = int(lineEditStart.text(), 16)
|
||||
stop = int(lineEditStop.text(), 16)
|
||||
self.det.patloop[level] = [start, stop]
|
||||
except Exception as e:
|
||||
QtWidgets.QMessageBox.warning(self.mainWindow, "Pattern Loop Start Stop Address Fail", str(e),
|
||||
QtWidgets.QMessageBox.Ok)
|
||||
pass
|
||||
# TODO: handling double event exceptions
|
||||
lineEditStart.editingFinished.connect(partial(self.setPatLoopStartStopAddress, level))
|
||||
lineEditStop.editingFinished.connect(partial(self.setPatLoopStartStopAddress, level))
|
||||
self.getPatLoopStartStopAddress(level)
|
||||
|
||||
def getPatLoopWaitAddress(self, level):
|
||||
retval = self.det.patwait[level]
|
||||
lineEdit = getattr(self.view, f"lineEditLoop{level}Wait")
|
||||
lineEdit.editingFinished.disconnect()
|
||||
lineEdit.setText("0x{:04x}".format(retval))
|
||||
lineEdit.editingFinished.connect(partial(self.setPatLoopWaitAddress, level))
|
||||
|
||||
def setPatLoopWaitAddress(self, level):
|
||||
lineEdit = getattr(self.view, f"lineEditLoop{level}Wait")
|
||||
lineEdit.editingFinished.disconnect()
|
||||
try:
|
||||
addr = int(lineEdit.text(), 16)
|
||||
self.det.patwait[level] = addr
|
||||
except Exception as e:
|
||||
QtWidgets.QMessageBox.warning(self.mainWindow, "Pattern Wait Address Fail", str(e),
|
||||
QtWidgets.QMessageBox.Ok)
|
||||
pass
|
||||
# TODO: handling double event exceptions
|
||||
lineEdit.editingFinished.connect(partial(self.setPatLoopWaitAddress, level))
|
||||
self.getPatLoopWaitAddress(level)
|
||||
|
||||
def getPatLoopRepetition(self, level):
|
||||
retval = self.det.patnloop[level]
|
||||
spinBox = getattr(self.view, f"spinBoxLoop{level}Repetition")
|
||||
spinBox.editingFinished.disconnect()
|
||||
spinBox.setValue(retval)
|
||||
spinBox.editingFinished.connect(partial(self.setPatLoopRepetition, level))
|
||||
|
||||
def setPatLoopRepetition(self, level):
|
||||
spinBox = getattr(self.view, f"spinBoxLoop{level}Repetition")
|
||||
self.det.patnloop[level] = spinBox.value()
|
||||
self.getPatLoopRepetition(level)
|
||||
|
||||
def getPatLoopWaitTime(self, level):
|
||||
retval = self.det.patwaittime[level]
|
||||
spinBox = getattr(self.view, f"spinBoxLoop{level}WaitTime")
|
||||
spinBox.editingFinished.disconnect()
|
||||
spinBox.setValue(retval)
|
||||
spinBox.editingFinished.connect(partial(self.setPatLoopWaitTime, level))
|
||||
|
||||
def setPatLoopWaitTime(self, level):
|
||||
spinBox = getattr(self.view, f"spinBoxLoop{level}WaitTime")
|
||||
self.det.patwaittime[level] = spinBox.value()
|
||||
self.getPatLoopWaitTime(level)
|
||||
|
||||
def setCompiler(self):
|
||||
response = QtWidgets.QFileDialog.getOpenFileName(
|
||||
parent=self.mainWindow,
|
||||
caption="Select a compiler file",
|
||||
directory=str(Path.cwd()),
|
||||
# filter='README (*.md *.ui)'
|
||||
)
|
||||
if response[0]:
|
||||
self.view.lineEditCompiler.setText(response[0])
|
||||
|
||||
def setUncompiledPatternFile(self):
|
||||
filt = 'Pattern code(*.py *.c)'
|
||||
folder = Path(self.det.patfname[0]).parent
|
||||
if not folder:
|
||||
folder = Path.cwd()
|
||||
response = QtWidgets.QFileDialog.getOpenFileName(parent=self.mainWindow,
|
||||
caption="Select an uncompiled pattern file",
|
||||
directory=str(folder),
|
||||
filter=filt)
|
||||
if response[0]:
|
||||
self.view.lineEditUncompiled.setText(response[0])
|
||||
|
||||
def setPatternFile(self):
|
||||
filt = 'Pattern file(*.pyat *.pat)'
|
||||
folder = Path(self.det.patfname[0]).parent
|
||||
if not folder:
|
||||
folder = Path.cwd()
|
||||
response = QtWidgets.QFileDialog.getOpenFileName(parent=self.mainWindow,
|
||||
caption="Select a compiled pattern file",
|
||||
directory=str(folder),
|
||||
filter=filt)
|
||||
if response[0]:
|
||||
self.view.lineEditPatternFile.setText(response[0])
|
||||
|
||||
def compilePattern(self):
|
||||
compilerFile = self.view.lineEditCompiler.text()
|
||||
if not compilerFile:
|
||||
QtWidgets.QMessageBox.warning(self.mainWindow, "Compile Fail", "No compiler selected. Please select one.",
|
||||
QtWidgets.QMessageBox.Ok)
|
||||
return ""
|
||||
|
||||
pattern_file = self.view.lineEditUncompiled.text()
|
||||
|
||||
# if old compile file exists, backup and remove to ensure old copy not loaded
|
||||
oldFile = Path(pattern_file + 'at')
|
||||
if oldFile.is_file():
|
||||
print("Moving old compiled pattern file to _bck")
|
||||
exit_status = os.system('mv ' + str(oldFile) + ' ' + str(oldFile) + '_bkup')
|
||||
if exit_status != 0:
|
||||
retval = QtWidgets.QMessageBox.question(
|
||||
self.mainWindow, "Backup Fail",
|
||||
"Could not make a backup of old compiled code. Proceed anyway to compile and overwrite?",
|
||||
QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
|
||||
if retval == QtWidgets.QMessageBox.No:
|
||||
return ""
|
||||
|
||||
compileCommand = compilerFile + ' ' + pattern_file
|
||||
print(compileCommand)
|
||||
print("Compiling pattern code to .pat file")
|
||||
exit_status = os.system(compileCommand)
|
||||
if exit_status != 0:
|
||||
QtWidgets.QMessageBox.warning(self.mainWindow, "Compile Fail", "Could not compile pattern.",
|
||||
QtWidgets.QMessageBox.Ok)
|
||||
return ""
|
||||
pattern_file += 'at'
|
||||
|
||||
return pattern_file
|
||||
|
||||
def getCompiledPatFname(self):
|
||||
if self.view.checkBoxCompile.isChecked():
|
||||
pattern_file = self.compilePattern()
|
||||
# pat name from pattern field
|
||||
else:
|
||||
pattern_file = self.view.lineEditPatternFile.text()
|
||||
if not pattern_file:
|
||||
QtWidgets.QMessageBox.warning(self.mainWindow, "Pattern Fail",
|
||||
"No pattern file selected. Please select one.", QtWidgets.QMessageBox.Ok)
|
||||
return ""
|
||||
return pattern_file
|
||||
|
||||
def loadPattern(self):
|
||||
pattern_file = self.getCompiledPatFname()
|
||||
if not pattern_file:
|
||||
return
|
||||
# load pattern
|
||||
self.det.pattern = pattern_file
|
||||
self.view.lineEditPatternFile.setText(self.det.patfname[0])
|
||||
|
||||
def getPatViewerColors(self):
|
||||
colorLevel = self.view.comboBoxPatColorSelect.currentIndex()
|
||||
color = self.colors_plot[colorLevel]
|
||||
self.view.comboBoxPatColor.currentIndexChanged.disconnect()
|
||||
self.view.comboBoxPatColor.setCurrentIndex(Defines.Colors.index(color))
|
||||
self.view.comboBoxPatColor.currentIndexChanged.connect(self.updatePatViewerParameters)
|
||||
|
||||
def getPatViewerWaitParameters(self):
|
||||
waitLevel = self.view.comboBoxPatWait.currentIndex()
|
||||
color = self.colors_wait[waitLevel]
|
||||
line_style = self.linestyles_wait[waitLevel]
|
||||
alpha = self.alpha_wait[waitLevel]
|
||||
alpha_rect = self.alpha_wait_rect[waitLevel]
|
||||
|
||||
self.view.comboBoxPatWaitColor.currentIndexChanged.disconnect()
|
||||
self.view.comboBoxPatWaitLineStyle.currentIndexChanged.disconnect()
|
||||
self.view.doubleSpinBoxWaitAlpha.editingFinished.disconnect()
|
||||
self.view.doubleSpinBoxWaitAlphaRect.editingFinished.disconnect()
|
||||
|
||||
self.view.comboBoxPatWaitColor.setCurrentIndex(Defines.Colors.index(color))
|
||||
self.view.comboBoxPatWaitLineStyle.setCurrentIndex(Defines.LineStyles.index(line_style))
|
||||
self.view.doubleSpinBoxWaitAlpha.setValue(alpha)
|
||||
self.view.doubleSpinBoxWaitAlphaRect.setValue(alpha_rect)
|
||||
|
||||
self.view.comboBoxPatWaitColor.currentIndexChanged.connect(self.updatePatViewerParameters)
|
||||
self.view.comboBoxPatWaitLineStyle.currentIndexChanged.connect(self.updatePatViewerParameters)
|
||||
self.view.doubleSpinBoxWaitAlpha.editingFinished.connect(self.updatePatViewerParameters)
|
||||
self.view.doubleSpinBoxWaitAlphaRect.editingFinished.connect(self.updatePatViewerParameters)
|
||||
|
||||
def getPatViewerLoopParameters(self):
|
||||
loopLevel = self.view.comboBoxPatLoop.currentIndex()
|
||||
color = self.colors_loop[loopLevel]
|
||||
line_style = self.linestyles_loop[loopLevel]
|
||||
alpha = self.alpha_loop[loopLevel]
|
||||
alpha_rect = self.alpha_loop_rect[loopLevel]
|
||||
|
||||
self.view.comboBoxPatLoopColor.currentIndexChanged.disconnect()
|
||||
self.view.comboBoxPatLoopLineStyle.currentIndexChanged.disconnect()
|
||||
self.view.doubleSpinBoxLoopAlpha.editingFinished.disconnect()
|
||||
self.view.doubleSpinBoxLoopAlphaRect.editingFinished.disconnect()
|
||||
|
||||
self.view.comboBoxPatLoopColor.setCurrentIndex(Defines.Colors.index(color))
|
||||
self.view.comboBoxPatLoopLineStyle.setCurrentIndex(Defines.LineStyles.index(line_style))
|
||||
self.view.doubleSpinBoxLoopAlpha.setValue(alpha)
|
||||
self.view.doubleSpinBoxLoopAlphaRect.setValue(alpha_rect)
|
||||
|
||||
self.view.comboBoxPatLoopColor.currentIndexChanged.connect(self.updatePatViewerParameters)
|
||||
self.view.comboBoxPatLoopLineStyle.currentIndexChanged.connect(self.updatePatViewerParameters)
|
||||
self.view.doubleSpinBoxLoopAlpha.editingFinished.connect(self.updatePatViewerParameters)
|
||||
self.view.doubleSpinBoxLoopAlphaRect.editingFinished.connect(self.updatePatViewerParameters)
|
||||
|
||||
# only at start up
|
||||
def updateDefaultPatViewerParameters(self):
|
||||
self.colors_plot = Defines.Colors_plot.copy()
|
||||
self.colors_wait = Defines.Colors_wait.copy()
|
||||
self.linestyles_wait = Defines.Linestyles_wait.copy()
|
||||
self.alpha_wait = Defines.Alpha_wait.copy()
|
||||
self.alpha_wait_rect = Defines.Alpha_wait_rect.copy()
|
||||
self.colors_loop = Defines.Colors_loop.copy()
|
||||
self.linestyles_loop = Defines.Linestyles_loop.copy()
|
||||
self.alpha_loop = Defines.Alpha_loop.copy()
|
||||
self.alpha_loop_rect = Defines.Alpha_loop_rect.copy()
|
||||
self.clock_vertical_lines_spacing = Defines.Clock_vertical_lines_spacing
|
||||
self.show_clocks_number = Defines.Show_clocks_number
|
||||
self.line_width = Defines.Line_width
|
||||
|
||||
# print('default')
|
||||
# self.printPatViewerParameters()
|
||||
|
||||
def updatePatViewerParameters(self):
|
||||
colorLevel = self.view.comboBoxPatColorSelect.currentIndex()
|
||||
color = self.view.comboBoxPatColor.currentIndex()
|
||||
# self.colors_plot[colorLevel] = f'tab:{Defines.Colors[color].lower()}'
|
||||
self.colors_plot[colorLevel] = Defines.Colors[color]
|
||||
|
||||
waitLevel = self.view.comboBoxPatWait.currentIndex()
|
||||
color = self.view.comboBoxPatWaitColor.currentIndex()
|
||||
line_style = self.view.comboBoxPatWaitLineStyle.currentIndex()
|
||||
alpha = self.view.doubleSpinBoxWaitAlpha.value()
|
||||
alpha_rect = self.view.doubleSpinBoxWaitAlphaRect.value()
|
||||
|
||||
self.colors_wait[waitLevel] = Defines.Colors[color]
|
||||
self.linestyles_wait[waitLevel] = Defines.LineStyles[line_style]
|
||||
self.alpha_wait[waitLevel] = alpha
|
||||
self.alpha_wait_rect[waitLevel] = alpha_rect
|
||||
|
||||
loopLevel = self.view.comboBoxPatLoop.currentIndex()
|
||||
color = self.view.comboBoxPatLoopColor.currentIndex()
|
||||
line_style = self.view.comboBoxPatLoopLineStyle.currentIndex()
|
||||
alpha = self.view.doubleSpinBoxLoopAlpha.value()
|
||||
alpha_rect = self.view.doubleSpinBoxLoopAlphaRect.value()
|
||||
|
||||
self.colors_loop[loopLevel] = Defines.Colors[color]
|
||||
self.linestyles_loop[loopLevel] = Defines.LineStyles[line_style]
|
||||
self.alpha_loop[loopLevel] = alpha
|
||||
self.alpha_loop_rect[loopLevel] = alpha_rect
|
||||
|
||||
self.clock_vertical_lines_spacing = self.view.spinBoxPatClockSpacing.value()
|
||||
self.show_clocks_number = self.view.checkBoxPatShowClockNumber.isChecked()
|
||||
self.line_width = self.view.doubleSpinBoxLineWidth.value()
|
||||
|
||||
# for debugging
|
||||
# self.printPatViewerParameters()
|
||||
|
||||
def printPatViewerParameters(self):
|
||||
print('Pattern Viewer Parameters:')
|
||||
print(f'\tcolor1: {self.colors_plot[0]}, color2: {self.colors_plot[1]}')
|
||||
print(f"\twait color: {self.colors_wait}")
|
||||
print(f"\twait linestyles: {self.linestyles_wait}")
|
||||
print(f"\twait alpha: {self.alpha_wait}")
|
||||
print(f"\twait alpha rect: {self.alpha_wait_rect}")
|
||||
print(f"\tloop color: {self.colors_loop}")
|
||||
print(f"\tloop linestyles: {self.linestyles_loop}")
|
||||
print(f"\tloop alpha: {self.alpha_loop}")
|
||||
print(f"\tloop alpha rect: {self.alpha_loop_rect}")
|
||||
print(f'\tclock vertical lines spacing: {self.clock_vertical_lines_spacing}')
|
||||
print(f'\tshow clocks number: {self.show_clocks_number}')
|
||||
print(f'\tline width: {self.line_width}')
|
||||
print('\n')
|
||||
|
||||
def viewPattern(self):
|
||||
self.plotTab.showPatternViewer(True)
|
||||
pattern_file = self.getCompiledPatFname()
|
||||
if not pattern_file:
|
||||
return
|
||||
|
||||
signalNames = self.det.getSignalNames()
|
||||
p = PlotPattern(
|
||||
pattern_file,
|
||||
signalNames,
|
||||
self.colors_plot,
|
||||
self.colors_wait,
|
||||
self.linestyles_wait,
|
||||
self.alpha_wait,
|
||||
self.alpha_wait_rect,
|
||||
self.colors_loop,
|
||||
self.linestyles_loop,
|
||||
self.alpha_loop,
|
||||
self.alpha_loop_rect,
|
||||
self.clock_vertical_lines_spacing,
|
||||
self.show_clocks_number,
|
||||
self.line_width,
|
||||
)
|
||||
|
||||
plt.close(self.figure)
|
||||
self.mainWindow.gridLayoutPatternViewer.removeWidget(self.canvas)
|
||||
self.canvas.close()
|
||||
self.mainWindow.gridLayoutPatternViewer.removeWidget(self.toolbar)
|
||||
self.toolbar.close()
|
||||
|
||||
try:
|
||||
self.figure = p.patternPlot()
|
||||
self.canvas = FigureCanvas(self.figure)
|
||||
self.toolbar = NavigationToolbar(self.canvas, self.view)
|
||||
self.mainWindow.gridLayoutPatternViewer.addWidget(self.toolbar)
|
||||
self.mainWindow.gridLayoutPatternViewer.addWidget(self.canvas)
|
||||
except Exception as e:
|
||||
QtWidgets.QMessageBox.warning(self.mainWindow, "Pattern Viewer Fail", str(e), QtWidgets.QMessageBox.Ok)
|
||||
pass
|
||||
|
||||
def saveParameters(self) -> list[str]:
|
||||
commands = []
|
||||
for i in range(Defines.pattern.loops_count):
|
||||
commands.append(f"patnloop {i} {getattr(self.view, f'spinBoxLoop{i}Repetition').text()}")
|
||||
commands.append(f"patloop {i} {getattr(self.view, f'lineEditLoop{i}Start').text()}, "
|
||||
f"{getattr(self.view, f'lineEditLoop{i}Stop').text()}")
|
||||
|
||||
commands.append(f"patwait {i} {getattr(self.view, f'lineEditLoop{i}Wait').text()}")
|
||||
commands.append(f"patwaittime {i} {getattr(self.view, f'spinBoxLoop{i}WaitTime').text()}")
|
||||
commands.append(f"patlimits {self.view.lineEditStartAddress.text()}, {self.view.lineEditStopAddress.text()}")
|
||||
# commands.append(f"patfname {self.view.lineEditPatternFile.text()}")
|
||||
return commands
|
||||
519
pyctbgui/pyctbgui/services/Plot.py
Normal file
519
pyctbgui/pyctbgui/services/Plot.py
Normal file
@@ -0,0 +1,519 @@
|
||||
import logging
|
||||
from functools import partial
|
||||
import random
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
from PyQt5 import QtWidgets, QtGui, uic
|
||||
|
||||
import pyqtgraph as pg
|
||||
from pyctbgui.utils import recordOrApplyPedestal
|
||||
from pyqtgraph import PlotWidget
|
||||
|
||||
from pyctbgui.utils.defines import Defines
|
||||
|
||||
|
||||
class PlotTab(QtWidgets.QWidget):
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
self.frame_min: float = 0.0
|
||||
self.frame_max: float = 0.0
|
||||
uic.loadUi(Path(__file__).parent.parent / 'ui' / "plot.ui", parent)
|
||||
self.view = parent
|
||||
self.mainWindow = None
|
||||
self.det = None
|
||||
self.signalsTab = None
|
||||
self.transceiverTab = None
|
||||
self.acquisitionTab = None
|
||||
self.adcTab = None
|
||||
self.cmin: float = 0.0
|
||||
self.cmax: float = 0.0
|
||||
self.colorRangeMode: Defines.colorRange = Defines.colorRange.all
|
||||
self.ignoreHistogramSignal: bool = False
|
||||
self.imagePlots: list[PlotWidget] = []
|
||||
# list of callback functions to notify tabs when we should hide their legend
|
||||
# follows the observer design pattern
|
||||
self.hideLegendObservers = []
|
||||
self.pedestalRecord: bool = False
|
||||
self.pedestalApply: bool = True
|
||||
self.__acqFrames = None
|
||||
self.logger = logging.getLogger('PlotTab')
|
||||
|
||||
def setup_ui(self):
|
||||
self.signalsTab = self.mainWindow.signalsTab
|
||||
self.transceiverTab = self.mainWindow.transceiverTab
|
||||
self.acquisitionTab = self.mainWindow.acquisitionTab
|
||||
self.adcTab = self.mainWindow.adcTab
|
||||
self.initializeColorMaps()
|
||||
|
||||
self.imagePlots = (
|
||||
self.mainWindow.plotAnalogImage,
|
||||
self.mainWindow.plotDigitalImage,
|
||||
self.mainWindow.plotTransceiverImage,
|
||||
)
|
||||
|
||||
def connect_ui(self):
|
||||
self.view.radioButtonNoPlot.clicked.connect(self.plotOptions)
|
||||
self.view.radioButtonWaveform.clicked.connect(self.plotOptions)
|
||||
self.view.radioButtonDistribution.clicked.connect(self.plotOptions)
|
||||
self.view.radioButtonImage.clicked.connect(self.plotOptions)
|
||||
self.view.comboBoxPlot.currentIndexChanged.connect(self.setPixelMap)
|
||||
self.view.comboBoxColorMap.currentIndexChanged.connect(self.setColorMap)
|
||||
self.view.comboBoxZMQHWM.currentIndexChanged.connect(self.setZMQHWM)
|
||||
self.view.spinBoxSerialOffset.editingFinished.connect(self.setSerialOffset)
|
||||
self.view.spinBoxNCount.editingFinished.connect(self.setNCounter)
|
||||
self.view.spinBoxDynamicRange.editingFinished.connect(self.setDynamicRange)
|
||||
self.view.spinBoxImageX.editingFinished.connect(self.setImageX)
|
||||
self.view.spinBoxImageY.editingFinished.connect(self.setImageY)
|
||||
|
||||
self.view.radioButtonPedestalRecord.toggled.connect(self.togglePedestalRecord)
|
||||
self.view.radioButtonPedestalApply.toggled.connect(self.togglePedestalApply)
|
||||
self.view.pushButtonPedestalReset.clicked.connect(self.resetPedestal)
|
||||
self.view.pushButtonSavePedestal.clicked.connect(self.savePedestal)
|
||||
self.view.pushButtonLoadPedestal.clicked.connect(self.loadPedestal)
|
||||
|
||||
self.view.checkBoxRaw.stateChanged.connect(self.setRawData)
|
||||
self.view.spinBoxRawMin.editingFinished.connect(self.setRawData)
|
||||
self.view.spinBoxRawMax.editingFinished.connect(self.setRawData)
|
||||
self.view.checkBoxPedestal.stateChanged.connect(self.setPedestalSubtract)
|
||||
self.view.spinBoxPedestalMin.editingFinished.connect(self.setPedestalSubtract)
|
||||
self.view.spinBoxPedestalMax.editingFinished.connect(self.setPedestalSubtract)
|
||||
self.view.spinBoxFit.editingFinished.connect(self.setFitADC)
|
||||
self.view.spinBoxPlot.editingFinished.connect(self.setPlotBit)
|
||||
self.view.pushButtonReferesh.clicked.connect(self.acquisitionTab.refresh)
|
||||
# color range widgets
|
||||
self.view.cminSpinBox.editingFinished.connect(self.setCmin)
|
||||
self.view.cmaxSpinBox.editingFinished.connect(self.setCmax)
|
||||
self.view.radioButtonAutomatic.clicked.connect(partial(self.setColorRangeMode, Defines.colorRange.all))
|
||||
self.view.radioButtonFixed.clicked.connect(partial(self.setColorRangeMode, Defines.colorRange.fixed))
|
||||
self.view.radioButtonCenter.clicked.connect(partial(self.setColorRangeMode, Defines.colorRange.center))
|
||||
|
||||
for plot in self.imagePlots:
|
||||
plot.scene.sigMouseMoved.connect(partial(self.showPlotValues, plot))
|
||||
plot.getHistogramWidget().item.sigLevelChangeFinished.connect(partial(self.handleHistogramChange, plot))
|
||||
|
||||
self.view.checkBoxShowLegend.stateChanged.connect(self.toggleLegend)
|
||||
|
||||
def refresh(self):
|
||||
self.getZMQHWM()
|
||||
|
||||
def initializeColorMaps(self):
|
||||
self.view.comboBoxColorMap.addItems(Defines.Color_map)
|
||||
self.view.comboBoxColorMap.setCurrentIndex(Defines.Color_map.index(Defines.Default_Color_Map))
|
||||
self.setColorMap()
|
||||
|
||||
def savePedestal(self):
|
||||
"""
|
||||
slot function to save pedestal values
|
||||
"""
|
||||
response = QtWidgets.QFileDialog.getSaveFileName(self.view, "Save Pedestal", str(self.det.fpath))
|
||||
recordOrApplyPedestal.savePedestal(Path(response[0]))
|
||||
self.logger.info(f'saved Pedestal in {response[0]}')
|
||||
|
||||
def loadPedestal(self):
|
||||
"""
|
||||
slot function to load pedestal values
|
||||
"""
|
||||
response = QtWidgets.QFileDialog.getOpenFileName(self.view, "Load Pedestal", str(self.det.fpath))
|
||||
if response[0] == '':
|
||||
return
|
||||
recordOrApplyPedestal.reset(self)
|
||||
try:
|
||||
recordOrApplyPedestal.loadPedestal(Path(response[0]))
|
||||
except (IsADirectoryError, ValueError, EOFError):
|
||||
QtWidgets.QMessageBox.warning(
|
||||
self.view,
|
||||
"Loading Pedestal Failed",
|
||||
"Loading Pedestal failed make sure the file is in the valid .npy format",
|
||||
QtWidgets.QMessageBox.Ok,
|
||||
)
|
||||
self.logger.exception("Exception when loading pedestal")
|
||||
else:
|
||||
self.logger.info(f'loaded Pedestal from {response[0]}')
|
||||
self.updateLabelPedestalFrames(True)
|
||||
|
||||
def togglePedestalRecord(self):
|
||||
"""
|
||||
slot function for pedestal record radio button
|
||||
toggle pedestal record variable and disables the frames spinboxes in acquisition tab or plot tab depenging on
|
||||
the mode
|
||||
"""
|
||||
self.pedestalRecord = not self.pedestalRecord
|
||||
|
||||
def togglePedestalApply(self):
|
||||
"""
|
||||
slot function for pedestal apply radio button
|
||||
"""
|
||||
self.pedestalApply = not self.pedestalApply
|
||||
|
||||
def resetPedestal(self):
|
||||
"""
|
||||
slot function for resetting the pedestal
|
||||
"""
|
||||
recordOrApplyPedestal.reset(self)
|
||||
|
||||
def updateLabelPedestalFrames(self, loadedPedestal=False):
|
||||
"""
|
||||
updates labelPedestalFrames to the length of savedFrames
|
||||
"""
|
||||
if loadedPedestal:
|
||||
self.view.labelPedestalFrames.setText('using loaded pedestal file')
|
||||
else:
|
||||
self.view.labelPedestalFrames.setText(f'recorded frames: {recordOrApplyPedestal.getFramesCount()}')
|
||||
|
||||
def subscribeToggleLegend(self, fn_cbk):
|
||||
"""
|
||||
subscribe to the event of toggling the hide legend checkbox by subscribing
|
||||
with a callback function
|
||||
"""
|
||||
self.hideLegendObservers.append(fn_cbk)
|
||||
|
||||
def toggleLegend(self):
|
||||
"""
|
||||
notify subscribers for the showLegend checkbox event by executing their callbacks
|
||||
"""
|
||||
self.mainWindow.showLegend = not self.mainWindow.showLegend
|
||||
for notify_function in self.hideLegendObservers:
|
||||
notify_function()
|
||||
|
||||
def setCmin(self, value=None):
|
||||
"""
|
||||
slot for setting cmin from cminSpinBox
|
||||
also used as a normal function
|
||||
"""
|
||||
if value is None:
|
||||
self.cmin = self.view.cminSpinBox.value()
|
||||
else:
|
||||
self.cmin = value
|
||||
self.updateColorRangeUI()
|
||||
|
||||
def setCmax(self, value=None):
|
||||
"""
|
||||
slot for setting cmax from cmaxSpinBox
|
||||
also used as a normal function
|
||||
"""
|
||||
if value is None:
|
||||
self.cmax = self.view.cmaxSpinBox.value()
|
||||
else:
|
||||
self.cmax = value
|
||||
self.updateColorRangeUI()
|
||||
|
||||
def setColorRangeMode(self, mode):
|
||||
"""
|
||||
slot for setting the color range mode (all,fixed,3-97%)
|
||||
"""
|
||||
self.colorRangeMode = mode
|
||||
|
||||
# disable or enable cmin/cmax spinboxes
|
||||
enableSpinBoxes = mode == Defines.colorRange.fixed
|
||||
self.view.cminSpinBox.setEnabled(enableSpinBoxes)
|
||||
self.view.cmaxSpinBox.setEnabled(enableSpinBoxes)
|
||||
self.updateColorRange()
|
||||
self.updateColorRangeUI()
|
||||
|
||||
def handleHistogramChange(self, plot):
|
||||
"""
|
||||
slot called after changing the color bar
|
||||
This is called even when pyqtgraph sets the image without any user intervention
|
||||
the class attribute ignore_histogram_signal is set to False before setting the image
|
||||
so that we can distinguish between the signal originates from pyqt or from the user
|
||||
"""
|
||||
if not self.ignoreHistogramSignal:
|
||||
self.cmin, self.cmax = plot.getHistogramWidget().item.getLevels()
|
||||
self.setCmin(self.cmin)
|
||||
self.setCmax(self.cmax)
|
||||
|
||||
self.ignoreHistogramSignal = False
|
||||
# self.setColorRangeMode(Defines.colorRange.fixed)
|
||||
|
||||
def setFrameLimits(self, frame):
|
||||
"""
|
||||
function called from AcquisitionTab::read_zmq to get the maximum and minimum
|
||||
values of the decoded frame and save them in frame_min/frame_max
|
||||
"""
|
||||
self.frame_min = np.min(frame)
|
||||
self.frame_max = np.max(frame)
|
||||
self.updateColorRange()
|
||||
|
||||
def updateColorRange(self):
|
||||
"""
|
||||
for mode:
|
||||
- all: sets cmin and cmax to the maximums/minimum values of the frame
|
||||
- 3-97%: limits cmax and cmin so that we ignore 3% from each end of the whole range
|
||||
- fixed: this function does not change cmin and cmax
|
||||
"""
|
||||
|
||||
if self.colorRangeMode == Defines.colorRange.all:
|
||||
self.cmin = self.frame_min
|
||||
self.cmax = self.frame_max
|
||||
elif self.colorRangeMode == Defines.colorRange.center:
|
||||
self.cmin = self.frame_min + 0.03 * (self.frame_max - self.frame_min)
|
||||
self.cmax = self.frame_max - 0.03 * (self.frame_max - self.frame_min)
|
||||
|
||||
self.updateColorRangeUI()
|
||||
|
||||
def updateColorRangeUI(self):
|
||||
"""
|
||||
updates UI views should be called after every change to cmin or cmax
|
||||
"""
|
||||
for plot in self.imagePlots:
|
||||
plot.getHistogramWidget().item.setLevels(min=self.cmin, max=self.cmax)
|
||||
self.view.cminSpinBox.setValue(self.cmin)
|
||||
self.view.cmaxSpinBox.setValue(self.cmax)
|
||||
|
||||
def setColorMap(self):
|
||||
cm = pg.colormap.getFromMatplotlib(self.view.comboBoxColorMap.currentText())
|
||||
# print(f'color map:{self.comboBoxColorMap.currentText()}')
|
||||
self.mainWindow.plotAnalogImage.setColorMap(cm)
|
||||
self.mainWindow.plotDigitalImage.setColorMap(cm)
|
||||
self.mainWindow.plotTransceiverImage.setColorMap(cm)
|
||||
|
||||
def getZMQHWM(self):
|
||||
|
||||
self.view.comboBoxZMQHWM.currentIndexChanged.disconnect()
|
||||
|
||||
rx_zmq_hwm = self.det.getRxZmqHwm()[0]
|
||||
# ensure same value in client zmq
|
||||
self.det.setClientZmqHwm(rx_zmq_hwm)
|
||||
|
||||
# high readout, low HWM
|
||||
if -1 < rx_zmq_hwm < 25:
|
||||
self.view.comboBoxZMQHWM.setCurrentIndex(1)
|
||||
# low readout, high HWM
|
||||
else:
|
||||
self.view.comboBoxZMQHWM.setCurrentIndex(0)
|
||||
self.view.comboBoxZMQHWM.currentIndexChanged.connect(self.setZMQHWM)
|
||||
|
||||
def setZMQHWM(self):
|
||||
val = self.view.comboBoxZMQHWM.currentIndex()
|
||||
# low readout, high HWM
|
||||
if val == 0:
|
||||
self.det.setRxZmqHwm(Defines.Zmq_hwm_low_speed)
|
||||
self.det.setClientZmqHwm(Defines.Zmq_hwm_low_speed)
|
||||
# high readout, low HWM
|
||||
else:
|
||||
self.det.setRxZmqHwm(Defines.Zmq_hwm_high_speed)
|
||||
self.det.setClientZmqHwm(Defines.Zmq_hwm_high_speed)
|
||||
|
||||
self.getZMQHWM()
|
||||
|
||||
def addSelectedAnalogPlots(self, i):
|
||||
enable = getattr(self.adcTab.view, f"checkBoxADC{i}Plot").isChecked()
|
||||
if enable:
|
||||
self.mainWindow.analogPlots[i].show()
|
||||
if not enable:
|
||||
self.mainWindow.analogPlots[i].hide()
|
||||
|
||||
def addAllSelectedAnalogPlots(self):
|
||||
for i in range(Defines.adc.count):
|
||||
self.addSelectedAnalogPlots(i)
|
||||
|
||||
def removeAllAnalogPlots(self):
|
||||
for i in range(Defines.adc.count):
|
||||
self.mainWindow.analogPlots[i].hide()
|
||||
|
||||
cm = pg.colormap.get('CET-L9') # prepare a linear color map
|
||||
self.mainWindow.plotDigitalImage.setColorMap(cm)
|
||||
|
||||
def addSelectedDigitalPlots(self, i):
|
||||
enable = getattr(self.signalsTab.view, f"checkBoxBIT{i}Plot").isChecked()
|
||||
if enable:
|
||||
self.mainWindow.digitalPlots[i].show()
|
||||
if not enable:
|
||||
self.mainWindow.digitalPlots[i].hide()
|
||||
|
||||
def addAllSelectedDigitalPlots(self):
|
||||
for i in range(Defines.signals.count):
|
||||
self.addSelectedDigitalPlots(i)
|
||||
|
||||
def removeAllDigitalPlots(self):
|
||||
for i in range(Defines.signals.count):
|
||||
self.mainWindow.digitalPlots[i].hide()
|
||||
|
||||
def addSelectedTransceiverPlots(self, i):
|
||||
enable = getattr(self.transceiverTab.view, f"checkBoxTransceiver{i}Plot").isChecked()
|
||||
if enable:
|
||||
self.mainWindow.transceiverPlots[i].show()
|
||||
if not enable:
|
||||
self.mainWindow.transceiverPlots[i].hide()
|
||||
|
||||
def addAllSelectedTransceiverPlots(self):
|
||||
for i in range(Defines.transceiver.count):
|
||||
self.addSelectedTransceiverPlots(i)
|
||||
|
||||
def removeAllTransceiverPlots(self):
|
||||
for i in range(Defines.transceiver.count):
|
||||
self.mainWindow.transceiverPlots[i].hide()
|
||||
|
||||
def showPlot(self):
|
||||
self.mainWindow.plotAnalogWaveform.hide()
|
||||
self.mainWindow.plotDigitalWaveform.hide()
|
||||
self.mainWindow.plotTransceiverWaveform.hide()
|
||||
self.mainWindow.plotAnalogImage.hide()
|
||||
self.mainWindow.plotDigitalImage.hide()
|
||||
self.mainWindow.plotTransceiverImage.hide()
|
||||
self.view.labelDigitalWaveformOption.setDisabled(True)
|
||||
self.view.radioButtonOverlay.setDisabled(True)
|
||||
self.view.radioButtonStripe.setDisabled(True)
|
||||
|
||||
if self.mainWindow.romode.value in [0, 2]:
|
||||
if self.view.radioButtonWaveform.isChecked():
|
||||
self.mainWindow.plotAnalogWaveform.show()
|
||||
elif self.view.radioButtonImage.isChecked():
|
||||
self.mainWindow.plotAnalogImage.show()
|
||||
if self.mainWindow.romode.value in [1, 2, 4]:
|
||||
if self.view.radioButtonWaveform.isChecked():
|
||||
self.mainWindow.plotDigitalWaveform.show()
|
||||
elif self.view.radioButtonImage.isChecked():
|
||||
self.mainWindow.plotDigitalImage.show()
|
||||
self.view.labelDigitalWaveformOption.setEnabled(True)
|
||||
self.view.radioButtonOverlay.setEnabled(True)
|
||||
self.view.radioButtonStripe.setEnabled(True)
|
||||
|
||||
if self.mainWindow.romode.value in [3, 4]:
|
||||
if self.view.radioButtonWaveform.isChecked():
|
||||
self.mainWindow.plotTransceiverWaveform.show()
|
||||
elif self.view.radioButtonImage.isChecked():
|
||||
self.mainWindow.plotTransceiverImage.show()
|
||||
|
||||
def plotOptions(self):
|
||||
|
||||
self.mainWindow.framePatternViewer.hide()
|
||||
self.showPlot()
|
||||
|
||||
# disable plotting
|
||||
self.mainWindow.read_timer.stop()
|
||||
|
||||
if self.view.radioButtonWaveform.isChecked():
|
||||
self.mainWindow.plotAnalogWaveform.setLabel(
|
||||
'left', "<span style=\"color:black;font-size:14px\">Output [ADC]</span>")
|
||||
self.mainWindow.plotAnalogWaveform.setLabel(
|
||||
'bottom', "<span style=\"color:black;font-size:14px\">Analog Sample [#]</span>")
|
||||
self.mainWindow.plotDigitalWaveform.setLabel(
|
||||
'left', "<span style=\"color:black;font-size:14px\">Digital Bit</span>")
|
||||
self.mainWindow.plotDigitalWaveform.setLabel(
|
||||
'bottom', "<span style=\"color:black;font-size:14px\">Digital Sample [#]</span>")
|
||||
self.mainWindow.plotTransceiverWaveform.setLabel(
|
||||
'left', "<span style=\"color:black;font-size:14px\">Transceiver Bit</span>")
|
||||
self.mainWindow.plotTransceiverWaveform.setLabel(
|
||||
'bottom', "<span style=\"color:black;font-size:14px\">Transceiver Sample [#]</span>")
|
||||
|
||||
self.view.stackedWidgetPlotType.setCurrentIndex(0)
|
||||
|
||||
elif self.view.radioButtonImage.isChecked():
|
||||
self.view.stackedWidgetPlotType.setCurrentIndex(2)
|
||||
self.setPixelMap()
|
||||
|
||||
if self.view.radioButtonNoPlot.isChecked():
|
||||
self.view.labelPlotOptions.hide()
|
||||
self.view.stackedWidgetPlotType.hide()
|
||||
# enable plotting
|
||||
else:
|
||||
self.view.labelPlotOptions.show()
|
||||
self.view.stackedWidgetPlotType.show()
|
||||
self.mainWindow.read_timer.start(Defines.Time_Plot_Refresh_ms)
|
||||
|
||||
def setPixelMap(self):
|
||||
if self.view.comboBoxPlot.currentText() == "Matterhorn":
|
||||
self.mainWindow.nTransceiverRows = Defines.Matterhorn.nRows
|
||||
self.mainWindow.nTransceiverCols = Defines.Matterhorn.nCols
|
||||
elif self.view.comboBoxPlot.currentText() == "Moench04":
|
||||
self.mainWindow.nAnalogRows = Defines.Moench04.nRows
|
||||
self.mainWindow.nAnalogCols = Defines.Moench04.nCols
|
||||
|
||||
def showPatternViewer(self, enable):
|
||||
if enable:
|
||||
self.mainWindow.framePatternViewer.show()
|
||||
self.mainWindow.framePlot.hide()
|
||||
elif self.mainWindow.framePatternViewer.isVisible():
|
||||
self.mainWindow.framePatternViewer.hide()
|
||||
self.mainWindow.framePlot.show()
|
||||
self.showPlot()
|
||||
|
||||
def setSerialOffset(self):
|
||||
print("plot options - Not implemented yet")
|
||||
# TODO:
|
||||
|
||||
def setNCounter(self):
|
||||
print("plot options - Not implemented yet")
|
||||
# TODO:
|
||||
|
||||
def setDynamicRange(self):
|
||||
print("plot options - Not implemented yet")
|
||||
# TODO:
|
||||
|
||||
def setImageX(self):
|
||||
print("plot options - Not implemented yet")
|
||||
# TODO:
|
||||
|
||||
def setImageY(self):
|
||||
print("plot options - Not implemented yet")
|
||||
# TODO:
|
||||
|
||||
def setRawData(self):
|
||||
print("plot options - Not implemented yet")
|
||||
# TODO: raw data, min, max
|
||||
|
||||
def setPedestalSubtract(self):
|
||||
print("plot options - Not implemented yet")
|
||||
# TODO: pedestal, min, max
|
||||
|
||||
def setFitADC(self):
|
||||
print("plot options - Not implemented yet")
|
||||
# TODO:
|
||||
|
||||
def setPlotBit(self):
|
||||
print("plot options - Not implemented yet")
|
||||
# TODO:
|
||||
|
||||
def getRandomColor(self):
|
||||
'''
|
||||
Returns a random color range (except white) in format string eg. "#aabbcc"
|
||||
'''
|
||||
randomColor = random.randrange(0, 0xffffaa, 0xaa)
|
||||
return "#{:06x}".format(randomColor)
|
||||
|
||||
def getActiveColor(self, button):
|
||||
return button.palette().color(QtGui.QPalette.Window)
|
||||
|
||||
def setActiveColor(self, button, str_color):
|
||||
button.setStyleSheet(":enabled {background-color: %s" % str_color + "} :disabled {background-color: grey}")
|
||||
|
||||
def showPalette(self, button):
|
||||
color = QtWidgets.QColorDialog.getColor()
|
||||
if color.isValid():
|
||||
self.setActiveColor(button, color.name())
|
||||
# get the RGB Values
|
||||
# print(color.getRgb())
|
||||
|
||||
def showPlotValues(self, sender, pos):
|
||||
x = sender.getImageItem().mapFromScene(pos).x()
|
||||
y = sender.getImageItem().mapFromScene(pos).y()
|
||||
val = 0
|
||||
nMaxY = self.mainWindow.nAnalogRows
|
||||
nMaxX = self.mainWindow.nAnalogCols
|
||||
frame = self.mainWindow.analog_frame
|
||||
if sender == self.mainWindow.plotDigitalImage:
|
||||
nMaxY = self.mainWindow.nDigitalRows
|
||||
nMaxX = self.mainWindow.nDigitalCols
|
||||
frame = self.mainWindow.digital_frame
|
||||
elif sender == self.mainWindow.plotTransceiverImage:
|
||||
nMaxY = self.mainWindow.nTransceiverRows
|
||||
nMaxX = self.mainWindow.nTransceiverCols
|
||||
frame = self.mainWindow.transceiver_frame
|
||||
if 0 <= x < nMaxX and 0 <= y < nMaxY and not np.array_equal(frame, []):
|
||||
val = frame[int(x), int(y)]
|
||||
message = f'[{x:.2f}, {y:.2f}] = {val:.2f}'
|
||||
sender.setToolTip(message)
|
||||
# print(message)
|
||||
else:
|
||||
sender.setToolTip('')
|
||||
|
||||
def saveParameters(self):
|
||||
commands = []
|
||||
if self.view.comboBoxZMQHWM.currentIndex() == 0:
|
||||
commands.append(f"zmqhwm {Defines.Zmq_hwm_low_speed}")
|
||||
else:
|
||||
commands.append(f"zmqhwm {Defines.Zmq_hwm_high_speed}")
|
||||
return commands
|
||||
123
pyctbgui/pyctbgui/services/PowerSupplies.py
Normal file
123
pyctbgui/pyctbgui/services/PowerSupplies.py
Normal file
@@ -0,0 +1,123 @@
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt5 import QtWidgets, uic
|
||||
from pyctbgui.utils.defines import Defines
|
||||
|
||||
from slsdet import dacIndex
|
||||
|
||||
|
||||
class PowerSuppliesTab(QtWidgets.QWidget):
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
uic.loadUi(Path(__file__).parent.parent / 'ui' / "powerSupplies.ui", parent)
|
||||
self.view = parent
|
||||
|
||||
def refresh(self):
|
||||
self.updateVoltageNames()
|
||||
for i in Defines.powerSupplies:
|
||||
self.getVoltage(i)
|
||||
self.getCurrent(i)
|
||||
|
||||
def connect_ui(self):
|
||||
for i in Defines.powerSupplies:
|
||||
spinBox = getattr(self.view, f"spinBoxV{i}")
|
||||
checkBox = getattr(self.view, f"checkBoxV{i}")
|
||||
spinBox.editingFinished.connect(partial(self.setVoltage, i))
|
||||
checkBox.stateChanged.connect(partial(self.setVoltage, i))
|
||||
self.view.pushButtonPowerOff.clicked.connect(self.powerOff)
|
||||
|
||||
def setup_ui(self):
|
||||
for i in Defines.powerSupplies:
|
||||
dac = getattr(dacIndex, f"V_POWER_{i}")
|
||||
spinBox = getattr(self.view, f"spinBoxV{i}")
|
||||
checkBox = getattr(self.view, f"checkBoxV{i}")
|
||||
retval = self.det.getPower(dac)[0]
|
||||
spinBox.setValue(retval)
|
||||
if retval == 0:
|
||||
checkBox.setChecked(False)
|
||||
spinBox.setDisabled(True)
|
||||
|
||||
def updateVoltageNames(self):
|
||||
retval = self.det.getPowerNames()
|
||||
getattr(self.view, "checkBoxVA").setText(retval[0])
|
||||
getattr(self.view, "checkBoxVB").setText(retval[1])
|
||||
getattr(self.view, "checkBoxVC").setText(retval[2])
|
||||
getattr(self.view, "checkBoxVD").setText(retval[3])
|
||||
getattr(self.view, "checkBoxVIO").setText(retval[4])
|
||||
|
||||
def getVoltage(self, i):
|
||||
spinBox = getattr(self.view, f"spinBoxV{i}")
|
||||
checkBox = getattr(self.view, f"checkBoxV{i}")
|
||||
voltageIndex = getattr(dacIndex, f"V_POWER_{i}")
|
||||
label = getattr(self.view, f"labelV{i}")
|
||||
|
||||
spinBox.editingFinished.disconnect()
|
||||
checkBox.stateChanged.disconnect()
|
||||
|
||||
retval = self.det.getMeasuredPower(voltageIndex)[0]
|
||||
# spinBox.setValue(retval)
|
||||
if retval > 1:
|
||||
checkBox.setChecked(True)
|
||||
if checkBox.isChecked():
|
||||
spinBox.setEnabled(True)
|
||||
else:
|
||||
spinBox.setDisabled(True)
|
||||
label.setText(f'{str(retval)} mV')
|
||||
|
||||
spinBox.editingFinished.connect(partial(self.setVoltage, i))
|
||||
checkBox.stateChanged.connect(partial(self.setVoltage, i))
|
||||
|
||||
self.getVChip()
|
||||
|
||||
# TODO: handle multiple events when pressing enter (twice)
|
||||
|
||||
def setVoltage(self, i):
|
||||
checkBox = getattr(self.view, f"checkBoxV{i}")
|
||||
spinBox = getattr(self.view, f"spinBoxV{i}")
|
||||
voltageIndex = getattr(dacIndex, f"V_POWER_{i}")
|
||||
spinBox.editingFinished.disconnect()
|
||||
|
||||
value = 0
|
||||
if checkBox.isChecked():
|
||||
value = spinBox.value()
|
||||
try:
|
||||
self.det.setPower(voltageIndex, value)
|
||||
except Exception as e:
|
||||
QtWidgets.QMessageBox.warning(self.mainWindow, "Voltage Fail", str(e), QtWidgets.QMessageBox.Ok)
|
||||
pass
|
||||
|
||||
# TODO: (properly) disconnecting and connecting to handle multiple events (out of focus and pressing enter).
|
||||
spinBox.editingFinished.connect(partial(self.setVoltage, i))
|
||||
self.getVoltage(i)
|
||||
self.getCurrent(i)
|
||||
|
||||
def getCurrent(self, i):
|
||||
label = getattr(self.view, f"labelI{i}")
|
||||
currentIndex = getattr(dacIndex, f"I_POWER_{i}")
|
||||
retval = self.det.getMeasuredCurrent(currentIndex)[0]
|
||||
label.setText(f'{str(retval)} mA')
|
||||
|
||||
def getVChip(self):
|
||||
self.view.spinBoxVChip.setValue(self.det.getPower(dacIndex.V_POWER_CHIP)[0])
|
||||
|
||||
def powerOff(self):
|
||||
for i in Defines.powerSupplies:
|
||||
# set all voltages to 0
|
||||
checkBox = getattr(self.view, f"checkBoxV{i}")
|
||||
checkBox.stateChanged.disconnect()
|
||||
checkBox.setChecked(False)
|
||||
checkBox.stateChanged.connect(partial(self.setVoltage, i))
|
||||
self.setVoltage(i)
|
||||
|
||||
def saveParameters(self) -> list:
|
||||
commands = []
|
||||
for i in Defines.powerSupplies:
|
||||
enabled = getattr(self.view, f"checkBoxV{i}").isChecked()
|
||||
if enabled:
|
||||
value = getattr(self.view, f"spinBoxV{i}").value()
|
||||
commands.append(f"v_{i.lower()} {value}")
|
||||
else:
|
||||
commands.append(f"v_{i.lower()} 0")
|
||||
return commands
|
||||
373
pyctbgui/pyctbgui/services/Signals.py
Normal file
373
pyctbgui/pyctbgui/services/Signals.py
Normal file
@@ -0,0 +1,373 @@
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
from PyQt5 import QtWidgets, uic
|
||||
import pyqtgraph as pg
|
||||
from pyqtgraph import LegendItem
|
||||
|
||||
from pyctbgui.utils.bit_utils import bit_is_set, manipulate_bit
|
||||
from pyctbgui.utils.defines import Defines
|
||||
from pyctbgui.utils.recordOrApplyPedestal import recordOrApplyPedestal
|
||||
|
||||
|
||||
class SignalsTab(QtWidgets.QWidget):
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
uic.loadUi(Path(__file__).parent.parent / 'ui' / "signals.ui", parent)
|
||||
self.view = parent
|
||||
self.mainWindow = None
|
||||
self.det = None
|
||||
self.plotTab = None
|
||||
self.legend: LegendItem | None = None
|
||||
self.rx_dbitoffset = None
|
||||
self.rx_dbitlist = None
|
||||
|
||||
def refresh(self):
|
||||
self.updateSignalNames()
|
||||
self.updateDigitalBitEnable()
|
||||
self.updateIOOut()
|
||||
self.getDBitOffset()
|
||||
|
||||
def connect_ui(self):
|
||||
for i in range(Defines.signals.count):
|
||||
getattr(self.view, f"checkBoxBIT{i}DB").stateChanged.connect(partial(self.setDigitalBitEnable, i))
|
||||
getattr(self.view, f"checkBoxBIT{i}Out").stateChanged.connect(partial(self.setIOOut, i))
|
||||
getattr(self.view, f"checkBoxBIT{i}Plot").stateChanged.connect(partial(self.setEnableBitPlot, i))
|
||||
getattr(self.view, f"pushButtonBIT{i}").clicked.connect(partial(self.selectBitColor, i))
|
||||
self.view.checkBoxBIT0_31DB.stateChanged.connect(
|
||||
partial(self.setDigitalBitEnableRange, 0, Defines.signals.half))
|
||||
self.view.checkBoxBIT32_63DB.stateChanged.connect(
|
||||
partial(self.setDigitalBitEnableRange, Defines.signals.half, Defines.signals.count))
|
||||
self.view.checkBoxBIT0_31Plot.stateChanged.connect(partial(self.setEnableBitPlotRange, 0,
|
||||
Defines.signals.half))
|
||||
self.view.checkBoxBIT32_63Plot.stateChanged.connect(
|
||||
partial(self.setEnableBitPlotRange, Defines.signals.half, Defines.signals.count))
|
||||
self.view.checkBoxBIT0_31Out.stateChanged.connect(partial(self.setIOOutRange, 0, Defines.signals.half))
|
||||
self.view.checkBoxBIT32_63Out.stateChanged.connect(
|
||||
partial(self.setIOOutRange, Defines.signals.half, Defines.signals.count))
|
||||
self.view.lineEditPatIOCtrl.editingFinished.connect(self.setIOOutReg)
|
||||
self.view.spinBoxDBitOffset.editingFinished.connect(self.setDbitOffset)
|
||||
|
||||
def setup_ui(self):
|
||||
self.plotTab = self.mainWindow.plotTab
|
||||
|
||||
for i in range(Defines.signals.count):
|
||||
self.setDBitButtonColor(i, self.plotTab.getRandomColor())
|
||||
|
||||
self.initializeAllDigitalPlots()
|
||||
|
||||
self.legend = self.mainWindow.plotDigitalWaveform.getPlotItem().legend
|
||||
self.legend.clear()
|
||||
# subscribe to toggle legend
|
||||
self.plotTab.subscribeToggleLegend(self.updateLegend)
|
||||
|
||||
def getEnabledPlots(self):
|
||||
"""
|
||||
return plots that are shown (checkBoxTransceiver{i}Plot is checked)
|
||||
"""
|
||||
enabledPlots = []
|
||||
self.legend.clear()
|
||||
for i in range(Defines.signals.count):
|
||||
if getattr(self.view, f'checkBoxBIT{i}Plot').isChecked():
|
||||
plotName = getattr(self.view, f"labelBIT{i}").text()
|
||||
enabledPlots.append((self.mainWindow.digitalPlots[i], plotName))
|
||||
return enabledPlots
|
||||
|
||||
def updateLegend(self):
|
||||
"""
|
||||
update the legend for the signals waveform plot
|
||||
should be called after checking or unchecking plot checkbox
|
||||
"""
|
||||
if not self.mainWindow.showLegend:
|
||||
self.legend.clear()
|
||||
else:
|
||||
for plot, name in self.getEnabledPlots():
|
||||
self.legend.addItem(plot, name)
|
||||
|
||||
@recordOrApplyPedestal
|
||||
def _processWaveformData(self, data, aSamples, dSamples, rx_dbitlist, isPlottedArray, rx_dbitoffset, romode,
|
||||
nADCEnabled):
|
||||
"""
|
||||
transform raw waveform data into a processed numpy array
|
||||
@param data: raw waveform data
|
||||
"""
|
||||
dbitoffset = rx_dbitoffset
|
||||
if romode == 2:
|
||||
dbitoffset += nADCEnabled * 2 * aSamples
|
||||
digital_array = np.array(np.frombuffer(data, offset=dbitoffset, dtype=np.uint8))
|
||||
nbitsPerDBit = dSamples
|
||||
if nbitsPerDBit % 8 != 0:
|
||||
nbitsPerDBit += (8 - (dSamples % 8))
|
||||
offset = 0
|
||||
arr = []
|
||||
for i in rx_dbitlist:
|
||||
# where numbits * numsamples is not a multiple of 8
|
||||
if offset % 8 != 0:
|
||||
offset += (8 - (offset % 8))
|
||||
if not isPlottedArray[i]:
|
||||
offset += nbitsPerDBit
|
||||
return None
|
||||
waveform = np.zeros(dSamples)
|
||||
for iSample in range(dSamples):
|
||||
# all samples for digital bit together from slsReceiver
|
||||
index = int(offset / 8)
|
||||
iBit = offset % 8
|
||||
bit = (digital_array[index] >> iBit) & 1
|
||||
waveform[iSample] = bit
|
||||
offset += 1
|
||||
arr.append(waveform)
|
||||
|
||||
return np.array(arr)
|
||||
|
||||
def processWaveformData(self, data, aSamples, dSamples):
|
||||
"""
|
||||
view function
|
||||
plots processed waveform data
|
||||
data: raw waveform data
|
||||
dsamples: digital samples
|
||||
asamples: analog samples
|
||||
"""
|
||||
waveforms = {}
|
||||
isPlottedArray = {i: getattr(self.view, f"checkBoxBIT{i}Plot").isChecked() for i in self.rx_dbitlist}
|
||||
|
||||
digital_array = self._processWaveformData(data, aSamples, dSamples, self.rx_dbitlist, isPlottedArray,
|
||||
self.rx_dbitoffset, self.mainWindow.romode.value,
|
||||
self.mainWindow.nADCEnabled)
|
||||
|
||||
irow = 0
|
||||
for idx, i in enumerate(self.rx_dbitlist):
|
||||
# bits enabled but not plotting
|
||||
waveform = digital_array[idx]
|
||||
if waveform is None:
|
||||
continue
|
||||
self.mainWindow.digitalPlots[i].setData(waveform)
|
||||
plotName = getattr(self.view, f"labelBIT{i}").text()
|
||||
waveforms[plotName] = waveform
|
||||
# TODO: left axis does not show 0 to 1, but keeps increasing
|
||||
if self.plotTab.view.radioButtonStripe.isChecked():
|
||||
self.mainWindow.digitalPlots[i].setY(irow * 2)
|
||||
irow += 1
|
||||
else:
|
||||
self.mainWindow.digitalPlots[i].setY(0)
|
||||
return waveforms
|
||||
|
||||
def initializeAllDigitalPlots(self):
|
||||
self.mainWindow.plotDigitalWaveform = pg.plot()
|
||||
self.mainWindow.plotDigitalWaveform.addLegend(colCount=Defines.colCount)
|
||||
self.mainWindow.verticalLayoutPlot.addWidget(self.mainWindow.plotDigitalWaveform, 3)
|
||||
self.mainWindow.digitalPlots = {}
|
||||
waveform = np.zeros(1000)
|
||||
for i in range(Defines.signals.count):
|
||||
pen = pg.mkPen(color=self.getDBitButtonColor(i), width=1)
|
||||
legendName = getattr(self.view, f"labelBIT{i}").text()
|
||||
self.mainWindow.digitalPlots[i] = self.mainWindow.plotDigitalWaveform.plot(waveform,
|
||||
pen=pen,
|
||||
name=legendName,
|
||||
stepMode="left")
|
||||
self.mainWindow.digitalPlots[i].hide()
|
||||
|
||||
self.mainWindow.plotDigitalImage = pg.ImageView()
|
||||
self.mainWindow.nDigitalRows = 0
|
||||
self.mainWindow.nDigitalCols = 0
|
||||
self.mainWindow.digital_frame = np.zeros((self.mainWindow.nDigitalRows, self.mainWindow.nDigitalCols))
|
||||
self.mainWindow.plotDigitalImage.setImage(self.mainWindow.digital_frame)
|
||||
self.mainWindow.verticalLayoutPlot.addWidget(self.mainWindow.plotDigitalImage, 4)
|
||||
|
||||
def updateSignalNames(self):
|
||||
for i, name in enumerate(self.det.getSignalNames()):
|
||||
getattr(self.view, f"labelBIT{i}").setText(name)
|
||||
|
||||
def getDigitalBitEnable(self, i, dbitList):
|
||||
checkBox = getattr(self.view, f"checkBoxBIT{i}DB")
|
||||
checkBox.stateChanged.disconnect()
|
||||
checkBox.setChecked(i in list(dbitList))
|
||||
checkBox.stateChanged.connect(partial(self.setDigitalBitEnable, i))
|
||||
|
||||
def updateDigitalBitEnable(self):
|
||||
retval = self.det.rx_dbitlist
|
||||
self.rx_dbitlist = list(retval)
|
||||
self.mainWindow.nDBitEnabled = len(list(retval))
|
||||
for i in range(Defines.signals.count):
|
||||
self.getDigitalBitEnable(i, retval)
|
||||
self.getEnableBitPlot(i)
|
||||
self.getEnableBitColor(i)
|
||||
self.plotTab.addSelectedDigitalPlots(i)
|
||||
self.getDigitalBitEnableRange(retval)
|
||||
self.getEnableBitPlotRange()
|
||||
|
||||
def setDigitalBitEnable(self, i):
|
||||
bitList = self.det.rx_dbitlist
|
||||
checkBox = getattr(self.view, f"checkBoxBIT{i}DB")
|
||||
if checkBox.isChecked():
|
||||
bitList.append(i)
|
||||
else:
|
||||
bitList.remove(i)
|
||||
self.det.rx_dbitlist = bitList
|
||||
|
||||
self.updateDigitalBitEnable()
|
||||
|
||||
def getDigitalBitEnableRange(self, dbitList):
|
||||
self.view.checkBoxBIT0_31DB.stateChanged.disconnect()
|
||||
self.view.checkBoxBIT32_63DB.stateChanged.disconnect()
|
||||
self.view.checkBoxBIT0_31DB.setChecked(all(x in list(dbitList) for x in range(Defines.signals.half)))
|
||||
self.view.checkBoxBIT32_63DB.setChecked(
|
||||
all(x in list(dbitList) for x in range(Defines.signals.half, Defines.signals.count)))
|
||||
self.view.checkBoxBIT0_31DB.stateChanged.connect(
|
||||
partial(self.setDigitalBitEnableRange, 0, Defines.signals.half))
|
||||
self.view.checkBoxBIT32_63DB.stateChanged.connect(
|
||||
partial(self.setDigitalBitEnableRange, Defines.signals.half, Defines.signals.count))
|
||||
|
||||
def setDigitalBitEnableRange(self, start_nr, end_nr):
|
||||
bitList = self.det.rx_dbitlist
|
||||
checkBox = getattr(self.view, f"checkBoxBIT{start_nr}_{end_nr - 1}DB")
|
||||
for i in range(start_nr, end_nr):
|
||||
if checkBox.isChecked():
|
||||
if i not in list(bitList):
|
||||
bitList.append(i)
|
||||
else:
|
||||
if i in list(bitList):
|
||||
bitList.remove(i)
|
||||
self.det.rx_dbitlist = bitList
|
||||
|
||||
self.updateDigitalBitEnable()
|
||||
|
||||
def getEnableBitPlot(self, i):
|
||||
checkBox = getattr(self.view, f"checkBoxBIT{i}DB")
|
||||
checkBoxPlot = getattr(self.view, f"checkBoxBIT{i}Plot")
|
||||
checkBoxPlot.setEnabled(checkBox.isChecked())
|
||||
|
||||
def setEnableBitPlot(self, i):
|
||||
pushButton = getattr(self.view, f"pushButtonBIT{i}")
|
||||
checkBox = getattr(self.view, f"checkBoxBIT{i}Plot")
|
||||
pushButton.setEnabled(checkBox.isChecked())
|
||||
|
||||
self.getEnableBitPlotRange()
|
||||
self.plotTab.addSelectedDigitalPlots(i)
|
||||
self.updateLegend()
|
||||
|
||||
def getEnableBitPlotRange(self):
|
||||
self.view.checkBoxBIT0_31Plot.stateChanged.disconnect()
|
||||
self.view.checkBoxBIT32_63Plot.stateChanged.disconnect()
|
||||
self.view.checkBoxBIT0_31Plot.setEnabled(
|
||||
all(getattr(self.view, f"checkBoxBIT{i}Plot").isEnabled() for i in range(Defines.signals.half)))
|
||||
self.view.checkBoxBIT32_63Plot.setEnabled(
|
||||
all(
|
||||
getattr(self.view, f"checkBoxBIT{i}Plot").isEnabled()
|
||||
for i in range(Defines.signals.half, Defines.signals.count)))
|
||||
self.view.checkBoxBIT0_31Plot.setChecked(
|
||||
all(getattr(self.view, f"checkBoxBIT{i}Plot").isChecked() for i in range(Defines.signals.half)))
|
||||
self.view.checkBoxBIT32_63Plot.setChecked(
|
||||
all(
|
||||
getattr(self.view, f"checkBoxBIT{i}Plot").isChecked()
|
||||
for i in range(Defines.signals.half, Defines.signals.count)))
|
||||
self.view.checkBoxBIT0_31Plot.stateChanged.connect(partial(self.setEnableBitPlotRange, 0,
|
||||
Defines.signals.half))
|
||||
self.view.checkBoxBIT32_63Plot.stateChanged.connect(
|
||||
partial(self.setEnableBitPlotRange, Defines.signals.half, Defines.signals.count))
|
||||
|
||||
def setEnableBitPlotRange(self, start_nr, end_nr):
|
||||
checkBox = getattr(self.view, f"checkBoxBIT{start_nr}_{end_nr - 1}Plot")
|
||||
enable = checkBox.isChecked()
|
||||
for i in range(start_nr, end_nr):
|
||||
checkBox = getattr(self.view, f"checkBoxBIT{i}Plot")
|
||||
checkBox.setChecked(enable)
|
||||
self.plotTab.addAllSelectedDigitalPlots()
|
||||
|
||||
def getEnableBitColor(self, i):
|
||||
checkBox = getattr(self.view, f"checkBoxBIT{i}Plot")
|
||||
pushButton = getattr(self.view, f"pushButtonBIT{i}")
|
||||
pushButton.setEnabled(checkBox.isEnabled() and checkBox.isChecked())
|
||||
|
||||
def selectBitColor(self, i):
|
||||
pushButton = getattr(self.view, f"pushButtonBIT{i}")
|
||||
self.plotTab.showPalette(pushButton)
|
||||
pen = pg.mkPen(color=self.getDBitButtonColor(i), width=1)
|
||||
self.mainWindow.digitalPlots[i].setPen(pen)
|
||||
|
||||
def getDBitButtonColor(self, i):
|
||||
pushButton = getattr(self.view, f"pushButtonBIT{i}")
|
||||
return self.plotTab.getActiveColor(pushButton)
|
||||
|
||||
def setDBitButtonColor(self, i, color):
|
||||
pushButton = getattr(self.view, f"pushButtonBIT{i}")
|
||||
return self.plotTab.setActiveColor(pushButton, color)
|
||||
|
||||
def getIOOutReg(self):
|
||||
retval = self.det.patioctrl
|
||||
self.view.lineEditPatIOCtrl.editingFinished.disconnect()
|
||||
self.view.lineEditPatIOCtrl.setText("0x{:016x}".format(retval))
|
||||
self.view.lineEditPatIOCtrl.editingFinished.connect(self.setIOOutReg)
|
||||
return retval
|
||||
|
||||
def setIOOutReg(self):
|
||||
self.view.lineEditPatIOCtrl.editingFinished.disconnect()
|
||||
try:
|
||||
self.det.patioctrl = int(self.view.lineEditPatIOCtrl.text(), 16)
|
||||
except Exception as e:
|
||||
QtWidgets.QMessageBox.warning(self.mainWindow, "IO Out Fail", str(e), QtWidgets.QMessageBox.Ok)
|
||||
pass
|
||||
# TODO: handling double event exceptions
|
||||
self.view.lineEditPatIOCtrl.editingFinished.connect(self.setIOOutReg)
|
||||
self.updateIOOut()
|
||||
|
||||
def updateCheckBoxIOOut(self, i, out):
|
||||
checkBox = getattr(self.view, f"checkBoxBIT{i}Out")
|
||||
checkBox.stateChanged.disconnect()
|
||||
checkBox.setChecked(bit_is_set(out, i))
|
||||
checkBox.stateChanged.connect(partial(self.setIOOut, i))
|
||||
|
||||
def updateIOOut(self):
|
||||
retval = self.getIOOutReg()
|
||||
for i in range(Defines.signals.count):
|
||||
self.updateCheckBoxIOOut(i, retval)
|
||||
self.getIOoutRange(retval)
|
||||
|
||||
def setIOOut(self, i):
|
||||
out = self.det.patioctrl
|
||||
checkBox = getattr(self.view, f"checkBoxBIT{i}Out")
|
||||
mask = manipulate_bit(checkBox.isChecked(), out, i)
|
||||
self.det.patioctrl = mask
|
||||
|
||||
retval = self.getIOOutReg()
|
||||
self.updateCheckBoxIOOut(i, retval)
|
||||
self.getIOoutRange(retval)
|
||||
|
||||
def getIOoutRange(self, out):
|
||||
self.view.checkBoxBIT0_31Out.stateChanged.disconnect()
|
||||
self.view.checkBoxBIT32_63Out.stateChanged.disconnect()
|
||||
self.view.checkBoxBIT0_31Out.setChecked((out & Defines.signals.BIT0_31_MASK) == Defines.signals.BIT0_31_MASK)
|
||||
self.view.checkBoxBIT32_63Out.setChecked((out
|
||||
& Defines.signals.BIT32_63_MASK) == Defines.signals.BIT32_63_MASK)
|
||||
self.view.checkBoxBIT0_31Out.stateChanged.connect(partial(self.setIOOutRange, 0, Defines.signals.half))
|
||||
self.view.checkBoxBIT32_63Out.stateChanged.connect(
|
||||
partial(self.setIOOutRange, Defines.signals.half, Defines.signals.count))
|
||||
|
||||
def setIOOutRange(self, start_nr, end_nr):
|
||||
out = self.det.patioctrl
|
||||
checkBox = getattr(self.view, f"checkBoxBIT{start_nr}_{end_nr - 1}Out")
|
||||
mask = getattr(Defines.signals, f"BIT{start_nr}_{end_nr - 1}_MASK")
|
||||
if checkBox.isChecked():
|
||||
self.det.patioctrl = out | mask
|
||||
else:
|
||||
self.det.patioctrl = out & ~mask
|
||||
self.updateIOOut()
|
||||
|
||||
def getDBitOffset(self):
|
||||
self.view.spinBoxDBitOffset.editingFinished.disconnect()
|
||||
self.rx_dbitoffset = self.det.rx_dbitoffset
|
||||
self.view.spinBoxDBitOffset.setValue(self.rx_dbitoffset)
|
||||
self.view.spinBoxDBitOffset.editingFinished.connect(self.setDbitOffset)
|
||||
|
||||
def setDbitOffset(self):
|
||||
self.det.rx_dbitoffset = self.view.spinBoxDBitOffset.value()
|
||||
|
||||
def saveParameters(self) -> list:
|
||||
commands = []
|
||||
dblist = [str(i) for i in range(Defines.signals.count) if getattr(self.view, f"checkBoxBIT{i}DB").isChecked()]
|
||||
if len(dblist) > 0:
|
||||
commands.append(f"rx_dbitlist {', '.join(dblist)}")
|
||||
commands.append(f"rx_dbitoffset {self.view.spinBoxDBitOffset.value()}")
|
||||
commands.append(f"patioctrl {self.view.lineEditPatIOCtrl.text()}")
|
||||
return commands
|
||||
45
pyctbgui/pyctbgui/services/SlowADCs.py
Normal file
45
pyctbgui/pyctbgui/services/SlowADCs.py
Normal file
@@ -0,0 +1,45 @@
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt5 import uic, QtWidgets
|
||||
|
||||
from pyctbgui.utils.defines import Defines
|
||||
from slsdet import dacIndex
|
||||
|
||||
|
||||
class SlowAdcTab(QtWidgets.QWidget):
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
uic.loadUi(Path(__file__).parent.parent / 'ui' / "slowAdcs.ui", parent)
|
||||
self.view = parent
|
||||
self.mainWindow = None
|
||||
self.det = None
|
||||
|
||||
def setup_ui(self):
|
||||
pass
|
||||
|
||||
def connect_ui(self):
|
||||
for i in range(Defines.slowAdc.count):
|
||||
getattr(self.view, f"pushButtonSlowAdc{i}").clicked.connect(partial(self.updateSlowAdc, i))
|
||||
self.view.pushButtonTemp.clicked.connect(self.updateTemperature)
|
||||
|
||||
def refresh(self):
|
||||
self.updateSlowAdcNames()
|
||||
for i in range(Defines.slowAdc.count):
|
||||
self.updateSlowAdc(i)
|
||||
self.updateTemperature()
|
||||
|
||||
def updateSlowAdcNames(self):
|
||||
for i, name in enumerate(self.mainWindow.det.getSlowADCNames()):
|
||||
getattr(self.view, f"labelSlowAdc{i}").setText(name)
|
||||
|
||||
def updateSlowAdc(self, i):
|
||||
slowADCIndex = getattr(dacIndex, f"SLOW_ADC{i}")
|
||||
label = getattr(self.view, f"labelSlowAdcValue{i}")
|
||||
slowadc = (self.det.getSlowADC(slowADCIndex))[0] / 1000
|
||||
label.setText(f'{slowadc:.2f} mV')
|
||||
|
||||
def updateTemperature(self):
|
||||
slowadc = self.det.getTemperature(dacIndex.SLOW_ADC_TEMP)
|
||||
self.view.labelTempValue.setText(f'{str(slowadc[0])} °C')
|
||||
273
pyctbgui/pyctbgui/services/Transceiver.py
Normal file
273
pyctbgui/pyctbgui/services/Transceiver.py
Normal file
@@ -0,0 +1,273 @@
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
from PyQt5 import QtWidgets, uic
|
||||
import pyqtgraph as pg
|
||||
from pyqtgraph import LegendItem
|
||||
|
||||
from pyctbgui.utils import decoder
|
||||
from pyctbgui.utils.defines import Defines
|
||||
|
||||
from pyctbgui.utils.bit_utils import bit_is_set, manipulate_bit
|
||||
import pyctbgui.utils.pixelmap as pm
|
||||
from pyctbgui.utils.recordOrApplyPedestal import recordOrApplyPedestal
|
||||
|
||||
|
||||
class TransceiverTab(QtWidgets.QWidget):
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
uic.loadUi(Path(__file__).parent.parent / 'ui' / "transceiver.ui", parent)
|
||||
self.view = parent
|
||||
self.mainWindow = None
|
||||
self.det = None
|
||||
self.plotTab = None
|
||||
self.legend: LegendItem | None = None
|
||||
self.acquisitionTab = None
|
||||
|
||||
def setup_ui(self):
|
||||
self.plotTab = self.mainWindow.plotTab
|
||||
self.acquisitionTab = self.mainWindow.acquisitionTab
|
||||
for i in range(Defines.transceiver.count):
|
||||
self.setTransceiverButtonColor(i, self.plotTab.getRandomColor())
|
||||
self.initializeAllTransceiverPlots()
|
||||
|
||||
self.legend = self.mainWindow.plotTransceiverWaveform.getPlotItem().legend
|
||||
self.legend.clear()
|
||||
|
||||
# subscribe to toggle legend
|
||||
self.plotTab.subscribeToggleLegend(self.updateLegend)
|
||||
|
||||
def connect_ui(self):
|
||||
for i in range(Defines.transceiver.count):
|
||||
getattr(self.view, f"checkBoxTransceiver{i}").stateChanged.connect(partial(self.setTransceiverEnable, i))
|
||||
getattr(self.view,
|
||||
f"checkBoxTransceiver{i}Plot").stateChanged.connect(partial(self.setTransceiverEnablePlot, i))
|
||||
getattr(self.view, f"pushButtonTransceiver{i}").clicked.connect(partial(self.selectTransceiverColor, i))
|
||||
self.view.lineEditTransceiverMask.editingFinished.connect(self.setTransceiverEnableReg)
|
||||
|
||||
def refresh(self):
|
||||
self.updateTransceiverEnable()
|
||||
|
||||
def getEnabledPlots(self):
|
||||
"""
|
||||
return plots that are shown (checkBoxTransceiver{i}Plot is checked)
|
||||
"""
|
||||
enabledPlots = []
|
||||
self.legend.clear()
|
||||
for i in range(Defines.transceiver.count):
|
||||
if getattr(self.view, f'checkBoxTransceiver{i}Plot').isChecked():
|
||||
plotName = getattr(self.view, f"labelTransceiver{i}").text()
|
||||
enabledPlots.append((self.mainWindow.transceiverPlots[i], plotName))
|
||||
return enabledPlots
|
||||
|
||||
def updateLegend(self):
|
||||
"""
|
||||
update the legend for the transceiver waveform plot
|
||||
should be called after checking or unchecking plot checkbox
|
||||
"""
|
||||
if not self.mainWindow.showLegend:
|
||||
self.legend.clear()
|
||||
else:
|
||||
for plot, name in self.getEnabledPlots():
|
||||
self.legend.addItem(plot, name)
|
||||
|
||||
@recordOrApplyPedestal
|
||||
def _processWaveformData(self, data, dSamples, romode, nDBitEnabled, nTransceiverEnabled):
|
||||
"""
|
||||
model function
|
||||
processes raw receiver waveform data
|
||||
@param data: raw receiver waveform data
|
||||
@param dSamples: digital samples
|
||||
@param romode: readout mode value
|
||||
@param nDBitEnabled: number of digital bits enabled
|
||||
@param nTransceiverEnabled: number of transceivers enabled
|
||||
@return: processed transceiver data
|
||||
"""
|
||||
transceiverOffset = 0
|
||||
if romode == 4:
|
||||
nbitsPerDBit = dSamples
|
||||
if dSamples % 8 != 0:
|
||||
nbitsPerDBit += (8 - (dSamples % 8))
|
||||
transceiverOffset += nDBitEnabled * (nbitsPerDBit // 8)
|
||||
trans_array = np.array(np.frombuffer(data, offset=transceiverOffset, dtype=np.uint16))
|
||||
return trans_array.reshape(-1, nTransceiverEnabled)
|
||||
|
||||
def processWaveformData(self, data, dSamples):
|
||||
"""
|
||||
plots raw waveform data
|
||||
data: raw waveform data
|
||||
dsamples: digital samples
|
||||
tsamples: transceiver samples
|
||||
"""
|
||||
waveforms = {}
|
||||
trans_array = self._processWaveformData(data, dSamples, self.mainWindow.romode.value,
|
||||
self.mainWindow.nDBitEnabled, self.nTransceiverEnabled)
|
||||
idx = 0
|
||||
for i in range(Defines.transceiver.count):
|
||||
checkBoxPlot = getattr(self.view, f"checkBoxTransceiver{i}Plot")
|
||||
checkBoxEn = getattr(self.view, f"checkBoxTransceiver{i}")
|
||||
if checkBoxEn.isChecked() and checkBoxPlot.isChecked():
|
||||
waveform = trans_array[:, idx]
|
||||
idx += 1
|
||||
self.mainWindow.transceiverPlots[i].setData(waveform)
|
||||
plotName = getattr(self.view, f"labelTransceiver{i}").text()
|
||||
waveforms[plotName] = waveform
|
||||
return waveforms
|
||||
|
||||
@recordOrApplyPedestal
|
||||
def _processImageData(self, data, dSamples, romode, nDBitEnabled):
|
||||
"""
|
||||
processes raw image data
|
||||
@param data:
|
||||
@param dSamples:
|
||||
@param romode:
|
||||
@param nDBitEnabled:
|
||||
@return:
|
||||
"""
|
||||
transceiverOffset = 0
|
||||
if romode == 4:
|
||||
nbitsPerDBit = dSamples
|
||||
if dSamples % 8 != 0:
|
||||
nbitsPerDBit += (8 - (dSamples % 8))
|
||||
transceiverOffset += nDBitEnabled * (nbitsPerDBit // 8)
|
||||
trans_array = np.array(np.frombuffer(data, offset=transceiverOffset, dtype=np.uint16))
|
||||
return decoder.decode(trans_array, pm.matterhorn_transceiver())
|
||||
|
||||
def processImageData(self, data, dSamples):
|
||||
"""
|
||||
view function
|
||||
plots transceiver image
|
||||
dSamples: digital samples
|
||||
data: raw image data
|
||||
"""
|
||||
# get zoom state
|
||||
viewBox = self.mainWindow.plotTransceiverImage.getView()
|
||||
state = viewBox.getState()
|
||||
try:
|
||||
self.mainWindow.transceiver_frame = self._processImageData(data, dSamples, self.mainWindow.romode.value,
|
||||
self.mainWindow.nDBitEnabled)
|
||||
self.plotTab.ignoreHistogramSignal = True
|
||||
self.mainWindow.plotTransceiverImage.setImage(self.mainWindow.transceiver_frame)
|
||||
except Exception:
|
||||
self.mainWindow.statusbar.setStyleSheet("color:red")
|
||||
message = f'Warning: Invalid size for Transceiver Image. Expected' \
|
||||
f' {self.mainWindow.nTransceiverRows * self.mainWindow.nTransceiverCols} size,' \
|
||||
f' got {self.mainWindow.transceiver_frame.size} instead.'
|
||||
self.acquisitionTab.updateCurrentFrame('Invalid Image')
|
||||
self.mainWindow.statusbar.showMessage(message)
|
||||
print(message)
|
||||
|
||||
self.plotTab.setFrameLimits(self.mainWindow.transceiver_frame)
|
||||
|
||||
# keep the zoomed in state (not 1st image)
|
||||
if self.mainWindow.firstTransceiverImage:
|
||||
self.mainWindow.firstTransceiverImage = False
|
||||
else:
|
||||
viewBox.setState(state)
|
||||
return self.mainWindow.transceiver_frame
|
||||
|
||||
def initializeAllTransceiverPlots(self):
|
||||
self.mainWindow.plotTransceiverWaveform = pg.plot()
|
||||
self.mainWindow.plotTransceiverWaveform.addLegend(colCount=Defines.colCount)
|
||||
self.mainWindow.verticalLayoutPlot.addWidget(self.mainWindow.plotTransceiverWaveform, 5)
|
||||
self.mainWindow.transceiverPlots = {}
|
||||
waveform = np.zeros(1000)
|
||||
for i in range(Defines.transceiver.count):
|
||||
pen = pg.mkPen(color=self.getTransceiverButtonColor(i), width=1)
|
||||
legendName = getattr(self.view, f"labelTransceiver{i}").text()
|
||||
self.mainWindow.transceiverPlots[i] = self.mainWindow.plotTransceiverWaveform.plot(waveform,
|
||||
pen=pen,
|
||||
name=legendName)
|
||||
self.mainWindow.transceiverPlots[i].hide()
|
||||
|
||||
self.mainWindow.plotTransceiverImage = pg.ImageView()
|
||||
self.mainWindow.nTransceiverRows = 0
|
||||
self.mainWindow.nTransceiverCols = 0
|
||||
self.mainWindow.transceiver_frame = np.zeros(
|
||||
(self.mainWindow.nTransceiverRows, self.mainWindow.nTransceiverCols))
|
||||
self.mainWindow.plotTransceiverImage.setImage(self.mainWindow.transceiver_frame)
|
||||
self.mainWindow.verticalLayoutPlot.addWidget(self.mainWindow.plotTransceiverImage, 6)
|
||||
|
||||
cm = pg.colormap.get('CET-L9') # prepare a linear color map
|
||||
self.mainWindow.plotTransceiverImage.setColorMap(cm)
|
||||
|
||||
def getTransceiverEnableReg(self):
|
||||
retval = self.det.transceiverenable
|
||||
self.view.lineEditTransceiverMask.editingFinished.disconnect()
|
||||
self.view.lineEditTransceiverMask.setText("0x{:08x}".format(retval))
|
||||
self.view.lineEditTransceiverMask.editingFinished.connect(self.setTransceiverEnableReg)
|
||||
return retval
|
||||
|
||||
def setTransceiverEnableReg(self):
|
||||
self.view.lineEditTransceiverMask.editingFinished.disconnect()
|
||||
try:
|
||||
mask = int(self.view.lineEditTransceiverMask.text(), 16)
|
||||
self.det.transceiverenable = mask
|
||||
except Exception as e:
|
||||
QtWidgets.QMessageBox.warning(self.mainWindow, "Transceiver Enable Fail", str(e), QtWidgets.QMessageBox.Ok)
|
||||
pass
|
||||
# TODO: handling double event exceptions
|
||||
self.view.lineEditTransceiverMask.editingFinished.connect(self.setTransceiverEnableReg)
|
||||
self.updateTransceiverEnable()
|
||||
|
||||
def getTransceiverEnable(self, i, mask):
|
||||
checkBox = getattr(self.view, f"checkBoxTransceiver{i}")
|
||||
checkBox.stateChanged.disconnect()
|
||||
checkBox.setChecked(bit_is_set(mask, i))
|
||||
checkBox.stateChanged.connect(partial(self.setTransceiverEnable, i))
|
||||
|
||||
def updateTransceiverEnable(self):
|
||||
retval = self.getTransceiverEnableReg()
|
||||
self.nTransceiverEnabled = bin(retval).count('1')
|
||||
for i in range(4):
|
||||
self.getTransceiverEnable(i, retval)
|
||||
self.getTransceiverEnablePlot(i)
|
||||
self.getTransceiverEnableColor(i)
|
||||
self.plotTab.addSelectedTransceiverPlots(i)
|
||||
|
||||
def setTransceiverEnable(self, i):
|
||||
checkBox = getattr(self.view, f"checkBoxTransceiver{i}")
|
||||
try:
|
||||
enableMask = manipulate_bit(checkBox.isChecked(), self.det.transceiverenable, i)
|
||||
self.det.transceiverenable = enableMask
|
||||
except Exception as e:
|
||||
QtWidgets.QMessageBox.warning(self.mainWindow, "Transceiver Enable Fail", str(e), QtWidgets.QMessageBox.Ok)
|
||||
pass
|
||||
|
||||
self.updateTransceiverEnable()
|
||||
|
||||
def getTransceiverEnablePlot(self, i):
|
||||
checkBox = getattr(self.view, f"checkBoxTransceiver{i}")
|
||||
checkBoxPlot = getattr(self.view, f"checkBoxTransceiver{i}Plot")
|
||||
checkBoxPlot.setEnabled(checkBox.isChecked())
|
||||
|
||||
def setTransceiverEnablePlot(self, i):
|
||||
pushButton = getattr(self.view, f"pushButtonTransceiver{i}")
|
||||
checkBox = getattr(self.view, f"checkBoxTransceiver{i}Plot")
|
||||
pushButton.setEnabled(checkBox.isChecked())
|
||||
self.plotTab.addSelectedTransceiverPlots(i)
|
||||
self.updateLegend()
|
||||
|
||||
def getTransceiverEnableColor(self, i):
|
||||
checkBox = getattr(self.view, f"checkBoxTransceiver{i}Plot")
|
||||
pushButton = getattr(self.view, f"pushButtonTransceiver{i}")
|
||||
pushButton.setEnabled(checkBox.isEnabled() and checkBox.isChecked())
|
||||
|
||||
def selectTransceiverColor(self, i):
|
||||
pushButton = getattr(self.view, f"pushButtonTransceiver{i}")
|
||||
self.plotTab.showPalette(pushButton)
|
||||
pen = pg.mkPen(color=self.getTransceiverButtonColor(i), width=1)
|
||||
self.mainWindow.transceiverPlots[i].setPen(pen)
|
||||
|
||||
def getTransceiverButtonColor(self, i):
|
||||
pushButton = getattr(self.view, f"pushButtonTransceiver{i}")
|
||||
return self.plotTab.getActiveColor(pushButton)
|
||||
|
||||
def setTransceiverButtonColor(self, i, color):
|
||||
pushButton = getattr(self.view, f"pushButtonTransceiver{i}")
|
||||
return self.plotTab.setActiveColor(pushButton, color)
|
||||
|
||||
def saveParameters(self):
|
||||
return ["transceiverenable {}".format(self.view.lineEditTransceiverMask.text())]
|
||||
9
pyctbgui/pyctbgui/services/__init__.py
Normal file
9
pyctbgui/pyctbgui/services/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from .ADC import AdcTab
|
||||
from .Acquisition import AcquisitionTab
|
||||
from .DACs import DacTab
|
||||
from .Pattern import PatternTab
|
||||
from .Plot import PlotTab
|
||||
from .PowerSupplies import PowerSuppliesTab
|
||||
from .Signals import SignalsTab
|
||||
from .SlowADCs import SlowAdcTab
|
||||
from .Transceiver import TransceiverTab
|
||||
Reference in New Issue
Block a user