GUI: updated smart_rotation_panel functionality and included more widgets in the GUI

This commit is contained in:
2025-10-07 13:05:50 +02:00
parent 3a24fc2aa1
commit 694ecf54bd
+169 -11
View File
@@ -1,3 +1,5 @@
import math
from PySide6.QtCore import Slot, Qt, Signal
from PySide6.QtWidgets import QWidget, QGridLayout, QLabel, QFrame, QPushButton, QSpacerItem, QSizePolicy
@@ -14,6 +16,12 @@ class SimpleRotationSettingsPanel(QWidget):
super().__init__(parent)
self.xtal_size_dose_rate_MGy_s = None
self.xtal_x = None
self.xtal_y = None
self.xtal_z = None
self.target_dose_MGy = None
self.dose_rate_MGy_s = None
self._filename = ""
self.dtz = 120
self.n_images = None
@@ -21,6 +29,9 @@ class SimpleRotationSettingsPanel(QWidget):
self.image_time_s = None
self.image_angle = 0.2
self.__d = None
self._temperature = 100
self.__omega = 0
self._wilson_b = 0
self._layout = QGridLayout(self)
@@ -73,55 +84,201 @@ class SimpleRotationSettingsPanel(QWidget):
self._layout.addWidget(self.dtz_label, 6, 1, 1, 3)
self._layout.addWidget(QLabel("mm", parent=self), 6, 4)
self._layout.addWidget(QLabel("Target Dose", parent=self), 7, 0)
self.target_dose_label = QLabel(f"--", parent=self)
self.target_dose_label.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
self._layout.addWidget(self.target_dose_label, 7, 1, 1, 3)
self._layout.addWidget(QLabel("MGy", parent=self), 7, 4)
self._layout.addWidget(QLabel("Calculated Dose Rate", parent=self), 8, 0)
self.calculated_dose_rate_label = QLabel(f"--", parent=self)
self.calculated_dose_rate_label.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
self._layout.addWidget(self.calculated_dose_rate_label, 8, 1, 1, 3)
self._layout.addWidget(QLabel("MGy S^-1", parent=self), 8, 4)
self._layout.addWidget(QLabel("Temperature", parent=self), 9, 0)
self.temp_enter = NumberLineEdit(80, 330, decimals=2, default=100.0, parent=self)
self._layout.addWidget(self.temp_enter, 9, 1, 1, 3)
self._layout.addWidget(QLabel("K", parent=self), 9, 4)
self.temp_enter.newValue.connect(self.set_temperature)
self._layout.addWidget(QLabel("Start angle", parent=self), 10, 0)
self.start_angle = NumberLineEdit(-720, 720.0, 0.0, decimals=3, parent=self)
self._layout.addWidget(self.start_angle, 10, 1, 1, 3)
self._layout.addWidget(QLabel("°", parent=self), 10, 4)
self.omega_button = QPushButton("Ω")
self.omega_button.setFixedWidth(20)
self.omega_button.clicked.connect(self.update_omega_start)
self._layout.addWidget(self.omega_button, 10, 5)
self._layout.addWidget(QLabel("Wilson B Factor", parent=self), 11, 0)
self.wilson_b_label = QLabel(f"--", parent=self)
self.wilson_b_label.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
self._layout.addWidget(self.wilson_b_label, 11, 1, 1, 3)
self._layout.addWidget(QLabel("Å^2", parent=self), 11, 4)
self._layout.addWidget(QLabel("Crystal Size x from Grid Scan", parent=self), 12, 0)
self.xtal_x_label = QLabel(f"--", parent=self)
self.xtal_x_label.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
self._layout.addWidget(self.xtal_x_label, 12, 1, 1, 3)
self._layout.addWidget(QLabel("um", parent=self), 12, 4)
self._layout.addWidget(QLabel("Crystal Size y from Grid Scan", parent=self), 13, 0)
self.xtal_y_label = QLabel(f"--", parent=self)
self.xtal_y_label.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
self._layout.addWidget(self.xtal_y_label, 13, 1, 1, 3)
self._layout.addWidget(QLabel("um", parent=self), 13, 4)
self._layout.addWidget(QLabel("Crystal Size z from Grid Scan", parent=self), 14, 0)
self.xtal_z_label = QLabel(f"--", parent=self)
self.xtal_z_label.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
self._layout.addWidget(self.xtal_z_label, 14, 1, 1, 3)
self._layout.addWidget(QLabel("um", parent=self), 14, 4)
self._layout.addWidget(QLabel("Calculated Dose from crystal size", parent=self), 15, 0)
self.xtal_size_dose_label = QLabel(f"--", parent=self)
self.xtal_size_dose_label.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
self._layout.addWidget(self.xtal_size_dose_label, 15, 1, 1, 3)
self._layout.addWidget(QLabel("MGy", parent=self), 15, 4)
self._layout.addWidget(QLabel("X-ray Wavelength", parent=self), 16, 0)
self.wavelength_label = QLabel("--", parent=self)
self.wavelength_label.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
self._layout.addWidget(self.wavelength_label, 16, 1, 1, 3)
self._layout.addWidget(QLabel("Å", parent=self), 16, 4)
self._layout.addWidget(QLabel("Flux", parent=self), 17, 0)
self.flux_label = QLabel(f"--", parent=self)
self.flux_label.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
self._layout.addWidget(self.flux_label, 17, 1, 1, 3)
self._layout.addWidget(QLabel("ph s^-1", parent=self), 17, 4)
self._layout.addWidget(QLabel("Beam Size", parent=self), 18, 0)
self.beam_size_label = QLabel(f"--", parent=self)
self.beam_size_label.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
self._layout.addWidget(self.beam_size_label, 18, 1, 1, 3)
self._layout.addWidget(QLabel("um ^ 2", parent=self), 18, 4)
self._layout.addWidget(QLabel("Calculated Dose", parent=self), 19, 0)
self.calculated_dose_label = QLabel(f"--", parent=self)
self.calculated_dose_label.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
self._layout.addWidget(self.calculated_dose_label, 19, 1, 1, 3)
self._layout.addWidget(QLabel("MGy", parent=self), 19, 4)
# add vertical stretch between detector distance and the run button
self._layout.addItem(QSpacerItem(0, 0, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding), 7, 0, 1, 6)
self._layout.addItem(QSpacerItem(0, 0, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding), 20, 0, 1, 6)
# Run rotation button
self.run_rotation_button = QPushButton("Run rotation", parent=self)
self.run_rotation_button.setStyleSheet("color: rgb(78, 154, 6);")
self._layout.addWidget(self.run_rotation_button, 8, 0, 1, 6)
self.run_rotation_button.clicked.connect(self.run_measurement)
self._layout.addWidget(self.run_rotation_button, 21, 0, 1, 6)
@Slot(DAQStatusModel)
def update_daq_status(self, s: DAQStatusModel):
self.__d = s
best = getattr(s, "last_best_res", None)
if best is not None:
best_res = s.last_best_res
if best_res is not None:
# clamp to control limits and update field; this will also trigger recalculation
lo, hi = 0.8, 10.0
v = max(lo, min(hi, float(best)))
v = max(lo, min(hi, float(best_res)))
self.visible_res_enter.update_value(v)
self.set_visible_resolution(v)
best_b_factor = s.last_best_b_factor
if best_b_factor is not None:
self.wilson_b_label.setText(f"{best_b_factor:.2f}")
xtal_size = s.crystal_size
if xtal_size is not None:
# clamp to control limits and update field; this will also trigger recalculation
self.xtal_x = xtal_size.x
self.xtal_y = xtal_size.y
self.xtal_z = xtal_size.z
self.xtal_x_label.setText(f"{self.xtal_x:.2f}")
self.xtal_y_label.setText(f"{self.xtal_y:.2f}")
self.xtal_z_label.setText(f"{self.xtal_z:.2f}")
#self.omega.new_value(s.geom.omega_deg)
self.beam_size_label.setText(
f"<b>{s.geom.beam_size_mm.x * 1000.0}</b> x <b>{s.geom.beam_size_mm.y * 1000.0}</b>"
)
self.flux_label.setText(f"<b>{(s.bl.flux_ph_s / 1e9):.2f}</b>")
if s.diffraction.wavelength_angstrom is None:
self.wavelength_label.setText("<b>N/A</b>")
else:
self.wavelength_label.setText(f"<b>{s.diffraction.wavelength_angstrom:.3f}</b>")
self.update_calculated_labels()
@Slot(float)
def set_visible_resolution(self, v: float):
self.update_calculated_labels()
@Slot(float)
def set_temperature(self, v: float):
self.update_calculated_labels()
@Slot(str)
def update_filename(self, filename: str):
self._filename = filename
@Slot()
def update_omega_start(self):
self.start_angle.update_value(self.__omega)
def update_calculated_labels(self):
if self.__d is None:
return
flux = 2.5e11
flux = 2.5e11 #TODO link flux
#TODO add start angle
#TODO link beam energy
#TODO read resolution estiamtion from jfjoch
total_angle = self.angular_range_enter.value
d_vis = self.visible_res_enter.value
d_tar = 1/(1/d_vis + 0.1)
self.target_res_label.setText(f"{d_tar:.2f}")
#self.wilson_b_label.setText("--")
Kdose = 2000 / (self.__d.diffraction.wavelength_angstrom**2)
dose_rate_MGy_s = (flux / (self.__d.geom.beam_size_mm.x * 1000 * self.__d.geom.beam_size_mm.y * 1000 * Kdose)) / 1e6
target_dose_MGy = 10 * d_tar / 2.0
total_time_s = target_dose_MGy / dose_rate_MGy_s
beam_size_um_y = self.__d.geom.beam_size_mm.y * 1000
beam_size_um_x = self.__d.geom.beam_size_mm.x * 1000
self.dose_rate_MGy_s = (flux / (beam_size_um_x * beam_size_um_y * Kdose)) / 1e6
if self.xtal_y is None or self.xtal_z is None or beam_size_um_x is None or beam_size_um_y is None:
self.xtal_size_dose_rate_MGy_s = self.dose_rate_MGy_s
elif self.xtal_y > beam_size_um_y or self.xtal_z > beam_size_um_y :
multiplier_1 = max(self.xtal_y, beam_size_um_y)
multiplier_2 = max( self.xtal_z,beam_size_um_y)
new_beam_um_y = math.sqrt(multiplier_1*multiplier_2)
self.xtal_size_dose_rate_MGy_s = (flux / (beam_size_um_x * new_beam_um_y * Kdose)) / 1e6
else:
self.xtal_size_dose_rate_MGy_s = self.dose_rate_MGy_s
if self.temp_enter.value > 250:
self.target_dose_MGy = 0.1 * d_tar / 2.0 #TODO add user input
else:
self.target_dose_MGy = 10 * d_tar / 2.0
self.target_dose_label.setText(f"{self.target_dose_MGy:.2f}")
self.calculated_dose_rate_label.setText(f"{self.dose_rate_MGy_s:.2f}")
self.xtal_size_dose_label.setText(f"{self.xtal_size_dose_rate_MGy_s:.2f}")
total_time_s = self.target_dose_MGy / self.dose_rate_MGy_s
self.calculated_dose_label.setText(f"{self.xtal_size_dose_rate_MGy_s*total_time_s:.2f}")
self.image_angle = 0.2
self.image_angle_label.setText(f"{self.image_angle:.3f}")
self.n_images = total_angle / self.image_angle
self.image_time_s = total_time_s / self.n_images
if self.image_time_s < 0.01:
self.transmission = self.image_time_s / 0.01
image_time_s = 0.01
self.image_time_s = 0.01
else:
self.transmission = 1.0
@@ -134,6 +291,7 @@ class SimpleRotationSettingsPanel(QWidget):
self.dtz_label.setText(f"""<span style="color: red ; ">-</span>""")
elif self.dtz < self.__d.bl.dtz_min:
self.dtz_label.setText(f"""<span style="color: red ; ">{self.dtz:.2f}</span>""")
self.dtz = self.__d.bl.dtz_min
else:
self.dtz_label.setText(f"{self.dtz:.2f}")