Files
detektor/src/channel_calibration_dialog.py
Martin Kudlacek 2e9c246b40
Some checks failed
Build Detektor binarie / build_macos (push) Has been cancelled
Fixed validation of float input
2025-12-08 14:04:11 +01:00

243 lines
8.4 KiB
Python

import logging
from typing import List, Tuple, Dict
from uuid import uuid1
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QDoubleValidator
from PyQt6.QtWidgets import QDialog, QVBoxLayout, QLabel, QPushButton, QHBoxLayout, QWidget, QCheckBox, QButtonGroup, \
QRadioButton, QSplitter, QLineEdit, QGroupBox
from detektor_region import DetektorRegion, DetektorRegionState
from detektor_data import DetektorContainer
from widgets import RoundedColorRectangleWidget
class ChannelCalibrationDialog(QDialog):
_only_region = None
_radio_map: Dict[uuid1, QRadioButton] = {}
def __init__(self, region: DetektorRegion):
super().__init__()
self.region = region
self.setWindowTitle("Kalibrace kanálu")
self.setModal(True) # Set the dialog as modal (blocks main window)
self.resize(300, 150)
# Main layout
main_layout = QVBoxLayout()
self.setLayout(main_layout)
# Splitter for two columns
splitter = QSplitter()
splitter.setOrientation(Qt.Orientation.Horizontal) # Horizontal splitter for left/right columns
# Left GroupBox (Channels)
left_group = QGroupBox("Vyberte kanál")
left_layout = QVBoxLayout()
left_group.setLayout(left_layout)
# Right GroupBox (Settings)
right_group = QGroupBox("Nastavení kanálu")
right_layout = QVBoxLayout()
right_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
right_group.setLayout(right_layout)
splitter.addWidget(left_group)
splitter.addWidget(right_group)
splitter.setStretchFactor(0, 1) # 50%
splitter.setStretchFactor(1, 1) # 50%
main_layout.addWidget(splitter)
# Create a button group to ensure only one channel is active at a time
self.radio_group = QButtonGroup()
for ch in DetektorContainer().get().channels:
channel_layout = QHBoxLayout()
channel_layout.setContentsMargins(0, 0, 0, 0)
channel_widget = QWidget()
channel_widget.setLayout(channel_layout)
left_layout.addWidget(channel_widget)
channel_radio = QRadioButton(f"{ch.name}")
self._radio_map[ch.id] = channel_radio
self.radio_group.addButton(channel_radio) # Add to button group
channel_layout.addWidget(channel_radio)
channel_layout.addWidget(RoundedColorRectangleWidget(ch.color))
from PyQt6.QtCore import QLocale
float_validator = CustomDoubleValidator()
float_validator.setLocale(QLocale(QLocale.Language.C))
# Přičíst input
add_layout = QVBoxLayout()
add_label = QLabel("Přičíst hodnotu ke kanálu")
self.add_input = QLineEdit()
self.add_input.setValidator(float_validator)
self.add_input.setText('0')
add_layout.addWidget(add_label)
add_layout.addWidget(self.add_input)
# Vynásobit input
multiply_layout = QVBoxLayout()
multiply_label = QLabel("Vynásobit data kanálu hodnotou")
self.multiply_input = QLineEdit()
self.multiply_input.setValidator(float_validator)
self.multiply_input.setText('1')
multiply_layout.addWidget(multiply_label)
multiply_layout.addWidget(self.multiply_input)
only_region_checkbox = QCheckBox(f"Kalibrovat pouze výběr")
# initial state is taken from region state
self._only_region = (self.region.state != DetektorRegionState.UNSET)
only_region_checkbox.setEnabled(self._only_region)
only_region_checkbox.setChecked(self._only_region)
# on click, intert the property state
only_region_checkbox.stateChanged.connect(self.toggle_only_region)
# Add inputs to the right group box
right_layout.addLayout(add_layout)
right_layout.addLayout(multiply_layout)
right_layout.addWidget(only_region_checkbox)
# Buttons layout (aligned at the bottom, next to each other)
button_layout = QHBoxLayout()
calibrate_button = QPushButton("Kalibrovat")
calibrate_button.clicked.connect(self.accept) # Close the dialog when the button is clicked
button_layout.addWidget(calibrate_button)
cancel_button = QPushButton("Zrušit")
cancel_button.clicked.connect(self.close) # Close the dialog when the button is clicked
button_layout.addWidget(cancel_button)
# Add buttons to the main layout below both columns
main_layout.addLayout(button_layout)
# Show the dialog
self.exec()
def toggle_only_region(self):
self._only_region = not self._only_region
def accept(self):
for channel_id, radio in self._radio_map.items():
if radio.isChecked():
DetektorContainer().duplicate()
has_channel = False
for channel_id, radio in self._radio_map.items():
if radio.isChecked():
# get the current channel object of the current (duplicated dataset)
channel = DetektorContainer().get().get_channel_by_uuid(channel_id)
try:
# validate inputs
offset = float(self.add_input.text().replace(',', '.'))
multiple = float(self.multiply_input.text().replace(',', '.'))
except ValueError:
WrongDataDialog()
return
logging.debug(f'Calibrating channel {channel.name} +{offset} x{multiple}')
if self._only_region:
start, end = self.region.get_safe_region()
logging.debug(f'Calibrating only region {start} - {end}')
channel.calibrate_data(
offset=offset,
multiple=multiple,
start=start,
end=end
)
self.region.unset()
else:
channel.calibrate_data(
offset=offset,
multiple=multiple
)
has_channel = True
break
# if we got here, no channel has been selected
if not has_channel:
# TODO: revert the duplication, since nothing has changed
MissingChannelDialog()
else:
super().accept()
class MissingChannelDialog(QDialog):
def __init__(self):
super().__init__()
self.setWindowTitle("Vyberte kanál")
self.setModal(True) # Set the dialog as modal (blocks main window)
self.resize(400, 75)
# Main layout
main_layout = QVBoxLayout()
self.setLayout(main_layout)
label = QLabel("Vyberte kanál, na kterém chcete provést kalibraci")
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
main_layout.addWidget(label)
cancel_button = QPushButton("OK")
cancel_button.clicked.connect(self.close)
main_layout.addWidget(cancel_button)
# Show the dialog
self.exec()
class WrongDataDialog(QDialog):
def __init__(self):
super().__init__()
self.setWindowTitle("Špatná data")
self.setModal(True) # Set the dialog as modal (blocks main window)
self.resize(400, 75)
# Main layout
main_layout = QVBoxLayout()
self.setLayout(main_layout)
label = QLabel("Zadaná data nejsou platná čísla.")
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
main_layout.addWidget(label)
cancel_button = QPushButton("OK")
cancel_button.clicked.connect(self.close)
main_layout.addWidget(cancel_button)
# Show the dialog
self.exec()
class CustomDoubleValidator(QDoubleValidator):
def validate(self, input_str, pos):
# Accept both comma and dot as decimal separator for validation
if ',' in input_str:
# Allow comma as intermediate state so user can type it
test_str = input_str.replace(',', '.')
state, _, _ = super().validate(test_str, pos)
if state == QDoubleValidator.State.Acceptable:
return (QDoubleValidator.State.Intermediate, input_str, pos)
return (QDoubleValidator.State.Intermediate, input_str, pos)
return super().validate(input_str, pos)
def fixup(self, input_str):
# Actually replace comma with dot in the QLineEdit
return input_str.replace(',', '.')