Files
slsDetectorPackage/pyctbgui/pyctbgui/ui/MainWindow.py
Dhanya Thattil 5b61ff24bb 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>
2024-09-10 16:00:04 +02:00

370 lines
14 KiB
Python

import logging
from PyQt5 import QtWidgets, QtCore, uic
import argparse
import signal
import pyqtgraph as pg
from pathlib import Path
from functools import partial
from slsdet import Detector, dacIndex
from pyctbgui.services import TransceiverTab, DacTab, AdcTab, AcquisitionTab, SignalsTab, PatternTab, \
SlowAdcTab, PlotTab, PowerSuppliesTab
from pyctbgui.utils import alias_utility
from pyctbgui.utils.defines import Defines
class MainWindow(QtWidgets.QMainWindow):
signalShortcutAcquire = QtCore.pyqtSignal()
signalShortcutTabUp = QtCore.pyqtSignal()
signalShortcutTabDown = QtCore.pyqtSignal()
def __init__(self, *args, **kwargs):
parser = argparse.ArgumentParser()
parser.add_argument('-a', '--alias', help="Alias file complete path")
arglist, __ = parser.parse_known_args()
self.alias_file = arglist.alias
pg.setConfigOption("background", (247, 247, 247))
pg.setConfigOption("foreground", "k")
pg.setConfigOption('leftButtonPan', False)
super().__init__(*args, **kwargs)
uic.loadUi(Path(__file__).parent / "CtbGui.ui", self)
logging.basicConfig(encoding='utf-8', level=logging.INFO)
self.logger = logging.getLogger(__name__)
self.det = None
self.showLegend = True
self.settings = None
try:
self.det = Detector()
# ensure detector is up
self.det.detectorserverversion[0]
except Exception as e:
QtWidgets.QMessageBox.critical(self, "Connect Fail", str(e) + "Exiting Gui...", QtWidgets.QMessageBox.Ok)
raise
# get Tab Classes
self.plotTab: PlotTab = self.widgetPlot
self.slowAdcTab: SlowAdcTab = self.widgetSlowAdcs
self.dacTab: DacTab = self.widgetDacs
self.powerSuppliesTab: PowerSuppliesTab = self.widgetPowerSupplies
self.signalsTab: SignalsTab = self.widgetSignals
self.transceiverTab: TransceiverTab = self.widgetTransceiver
self.adcTab: AdcTab = self.widgetAdc
self.patternTab: PatternTab = self.widgetPattern
self.acquisitionTab: AcquisitionTab = self.widgetAcquisition
self.tabs_list = [
self.dacTab, self.powerSuppliesTab, self.slowAdcTab, self.signalsTab, self.transceiverTab, self.adcTab,
self.patternTab, self.acquisitionTab, self.plotTab
]
self.setup_ui()
self.acquisitionTab.setup_zmq()
self.tabWidget.setCurrentIndex(Defines.Acquisition_Tab_Index)
self.tabWidget.currentChanged.connect(self.refresh_tab)
self.connect_ui()
for tab in self.tabs_list:
tab.refresh()
# also refreshes timer to start plotting
self.plotTab.plotOptions()
self.plotTab.showPlot()
self.patternTab.getPatViewerColors()
self.patternTab.getPatViewerWaitParameters()
self.patternTab.getPatViewerLoopParameters()
self.patternTab.updatePatViewerParameters()
self.plotTab.showPatternViewer(False)
if self.alias_file is not None:
self.loadAliasFile()
self.signalShortcutAcquire.connect(self.pushButtonStart.click)
self.signalShortcutTabUp.connect(partial(self.changeTabIndex, True))
self.signalShortcutTabDown.connect(partial(self.changeTabIndex, False))
# to catch the ctrl + c to abort
signal.signal(signal.SIGINT, signal.SIG_DFL)
self.firstAnalogImage = True
self.firstDigitalImage = True
self.firstTransceiverImage = True
self.updateSettingValues()
def updateSettingMainWindow(self):
self.settings.beginGroup("mainwindow")
# window size
width = self.settings.value('window_width')
height = self.settings.value('window_height')
if width is not None and height is not None:
self.resize(int(width), int(height))
# print(f'Main window resized to {width}x{height}')
# window position
pos = self.settings.value('window_pos')
if type(pos) is QtCore.QPoint:
# print(f'Moved main window to {pos}')
self.move(pos)
self.settings.endGroup()
def saveSettingMainWindow(self):
self.settings.beginGroup("mainwindow")
self.settings.setValue('window_width', self.rect().width())
self.settings.setValue('window_height', self.rect().height())
self.settings.setValue('window_pos', self.pos())
self.settings.endGroup()
def updateSettingDockWidget(self):
self.settings.beginGroup("dockwidget")
# is docked
if self.settings.contains('window_width') and self.settings.contains('window_height'):
# window size
width = self.settings.value('window_width')
height = self.settings.value('window_height')
if width is not None and height is not None:
# print(f'Plot window - Floating ({width}x{height})')
self.dockWidget.setFloating(True)
self.dockWidget.resize(int(width), int(height))
# window position
pos = self.settings.value('window_pos')
if type(pos) is QtCore.QPoint:
# print(f'Moved plot window to {pos}')
self.dockWidget.move(pos)
self.settings.endGroup()
def saveSettingDockWidget(self):
self.settings.beginGroup("dockwidget")
if self.dockWidget.isFloating():
self.settings.setValue('window_width', self.dockWidget.rect().width())
self.settings.setValue('window_height', self.dockWidget.rect().height())
self.settings.setValue('window_pos', self.dockWidget.pos())
else:
self.settings.remove('window_width')
self.settings.remove('window_height')
self.settings.remove('window_pos')
self.settings.endGroup()
def savePlotTypeAndDetector(self):
self.settings.setValue('isImage', self.plotTab.view.radioButtonImage.isChecked())
self.settings.setValue('detector', self.plotTab.view.comboBoxPlot.currentText())
def updatePlotTypeAndDetector(self):
# load plot type from qsettings
isImage = self.settings.value('isImage', True, type=bool)
self.plotTab.view.radioButtonImage.setChecked(isImage)
self.plotTab.plotOptions()
# load detector from qsettings
if isImage:
self.plotTab.view.comboBoxPlot.setCurrentText(self.settings.value('detector', 'Matterhorn'))
def updateSettingValues(self):
self.settings = QtCore.QSettings('slsdetectorgroup', 'pyctbgui')
self.updateSettingMainWindow()
self.updateSettingDockWidget()
self.updatePlotTypeAndDetector()
def saveSettings(self):
# store in ~/.config/slsdetectorgroup/pyctbgui.conf
self.saveSettingMainWindow()
self.saveSettingDockWidget()
self.savePlotTypeAndDetector()
def closeEvent(self, event):
self.saveSettings()
def loadAliasFile(self):
print(f'Loading Alias file: {self.alias_file}')
try:
bit_names, bit_plots, bit_colors, adc_names, adc_plots, adc_colors, dac_names, slowadc_names, \
voltage_names, pat_file_name = alias_utility.read_alias_file(self.alias_file)
except Exception as e:
QtWidgets.QMessageBox.warning(self, "Alias File Fail",
str(e) + "<br> " + self.alias_file, QtWidgets.QMessageBox.Ok)
return
for i in range(Defines.signals.count):
if bit_names[i]:
self.det.setSignalName(i, bit_names[i])
if bit_plots[i]:
getattr(self.signalsTab.view, f"checkBoxBIT{i}DB").setChecked(bit_plots[i])
getattr(self.signalsTab.view, f"checkBoxBIT{i}Plot").setChecked(bit_plots[i])
if bit_colors[i]:
self.signalsTab.setDBitButtonColor(i, bit_colors[i])
for i in range(Defines.adc.count):
if adc_names[i]:
self.det.setAdcName(i, adc_names[i])
if adc_plots[i]:
getattr(self.adcTab.view, f"checkBoxADC{i}En").setChecked(adc_plots[i])
getattr(self.adcTab.view, f"checkBoxADC{i}Plot").setChecked(adc_plots[i])
if adc_colors[i]:
self.adcTab.setADCButtonColor(i, adc_colors[i])
for i in range(Defines.dac.count):
if dac_names[i]:
iDac = getattr(dacIndex, f"DAC_{i}")
self.det.setDacName(iDac, dac_names[i])
for i in range(Defines.slowAdc.count):
slowadc_index = self.det.getSlowADCList()
if slowadc_names[i]:
self.det.setSlowADCName(slowadc_index[i], slowadc_names[i])
for i in range(len(Defines.powerSupplies)):
voltage_index = self.det.getVoltageList()
if voltage_names[i]:
self.det.setVoltageName(voltage_index[i], voltage_names[i])
if pat_file_name:
self.lineEditPatternFile.setText(pat_file_name)
self.signalsTab.updateSignalNames()
self.adcTab.updateADCNames()
self.slowAdcTab.updateSlowAdcNames()
self.dacTab.updateDACNames()
self.powerSuppliesTab.updateVoltageNames()
# For Action options function
# TODO Only add the components of action option+ functions
# Function to show info
def showInfo(self):
msg = QtWidgets.QMessageBox()
msg.setWindowTitle("About")
msg.setText("This Gui is for Chip Test Boards.\n Current Phase: Development")
msg.exec_()
def showKeyBoardShortcuts(self):
msg = QtWidgets.QMessageBox()
msg.setWindowTitle("Keyboard Shortcuts")
msg.setText(
"Start Acquisition (from any tab): Shift + Return<br>Move Tab Right : Ctrl + '+'<br>Move Tab Left :"
" Ctrl + '-'<br>")
msg.exec_()
def loadParameters(self):
response = QtWidgets.QFileDialog.getOpenFileName(
parent=self,
caption="Select a parameter file to open",
directory=str(Path.cwd()),
# filter='README (*.md *.ui)'
)
if response[0] == '':
return
try:
self.det.parameters = (response[0])
for tab in self.tabs_list:
tab.refresh()
QtWidgets.QMessageBox.information(self, "Load Parameter Success", "Parameters loaded successfully",
QtWidgets.QMessageBox.Ok)
except (RuntimeError, FileNotFoundError) as e:
self.logger.exception(e)
QtWidgets.QMessageBox.warning(self, "Load Parameter Fail", str(e), QtWidgets.QMessageBox.Ok)
def refresh_tab(self, tab_index):
match tab_index:
case 0:
self.dacTab.refresh()
case 1:
self.powerSuppliesTab.refresh()
case 2:
self.slowAdcTab.refresh()
case 3:
self.transceiverTab.refresh()
case 4:
self.signalsTab.refresh()
case 5:
self.adcTab.refresh()
case 6:
self.patternTab.refresh()
case 7:
self.acquisitionTab.refresh()
case 8:
self.plotTab.refresh()
def setup_ui(self):
# To check detector status
self.statusTimer = QtCore.QTimer()
self.statusTimer.timeout.connect(self.acquisitionTab.checkEndofAcquisition)
# To auto trigger the read
self.read_timer = QtCore.QTimer()
self.read_timer.timeout.connect(self.acquisitionTab.read_zmq)
for tab in self.tabs_list:
tab.mainWindow = self
tab.det = self.det
for tab in self.tabs_list:
tab.setup_ui()
def keyPressEvent(self, event):
if event.modifiers() & QtCore.Qt.ShiftModifier:
if event.key() == QtCore.Qt.Key_Return:
self.signalShortcutAcquire.emit()
if event.modifiers() & QtCore.Qt.ControlModifier:
if event.key() == QtCore.Qt.Key_Plus:
self.signalShortcutTabUp.emit()
if event.key() == QtCore.Qt.Key_Minus:
self.signalShortcutTabDown.emit()
def changeTabIndex(self, up):
ind = self.tabWidget.currentIndex()
if up:
ind += 1
if ind == Defines.Max_Tabs:
ind = 0
else:
ind -= 1
if ind == -1:
ind = Defines.Max_Tabs - 1
self.tabWidget.setCurrentIndex(ind)
def connect_ui(self):
# Show info
self.actionInfo.triggered.connect(self.showInfo)
self.actionKeyboardShortcuts.triggered.connect(self.showKeyBoardShortcuts)
self.actionLoadParameters.triggered.connect(self.loadParameters)
self.pushButtonStart.clicked.connect(self.acquisitionTab.toggleAcquire)
self.actionSaveParameters.triggered.connect(self.saveParameters)
for tab in self.tabs_list:
tab.connect_ui()
def saveParameters(self):
response = QtWidgets.QFileDialog.getSaveFileName(self, "Save Parameters", str(self.det.fpath))
if response[0] == '':
return
# save DACs
commands = self.dacTab.saveParameters()
# save signals
commands.extend(self.signalsTab.saveParameters())
# save transceiver
commands.extend(self.transceiverTab.saveParameters())
# save ADCs
commands.extend(self.adcTab.saveParameters())
# save pattern
commands.extend(self.patternTab.saveParameters())
# save acquisition
commands.extend(self.acquisitionTab.saveParameters())
# save power supplies
commands.extend(self.powerSuppliesTab.saveParameters())
# save plot
commands.extend(self.plotTab.saveParameters())
try:
with open(response[0], 'w') as fp:
fp.write('\n'.join(commands))
except Exception as e:
self.logger.exception(e)
QtWidgets.QMessageBox.warning(self, "Save Parameter Fail", str(e), QtWidgets.QMessageBox.Ok)
QtWidgets.QMessageBox.information(self, "Save Parameter Success", "Parameters saved successfully",
QtWidgets.QMessageBox.Ok)