From 694ecf54bd0872d91f4490186f7bfe54d0878c2e Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Tue, 7 Oct 2025 13:05:50 +0200 Subject: [PATCH] GUI: updated smart_rotation_panel functionality and included more widgets in the GUI --- .../aaregui/panels/smart_rotation_panel.py | 180 ++++++++++++++++-- 1 file changed, 169 insertions(+), 11 deletions(-) diff --git a/gui/src/aaregui/panels/smart_rotation_panel.py b/gui/src/aaregui/panels/smart_rotation_panel.py index 2acc0fae..5c765d87 100644 --- a/gui/src/aaregui/panels/smart_rotation_panel.py +++ b/gui/src/aaregui/panels/smart_rotation_panel.py @@ -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"{s.geom.beam_size_mm.x * 1000.0} x {s.geom.beam_size_mm.y * 1000.0}" + ) + self.flux_label.setText(f"{(s.bl.flux_ph_s / 1e9):.2f}") + if s.diffraction.wavelength_angstrom is None: + self.wavelength_label.setText("N/A") + else: + self.wavelength_label.setText(f"{s.diffraction.wavelength_angstrom:.3f}") 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"""-""") elif self.dtz < self.__d.bl.dtz_min: self.dtz_label.setText(f"""{self.dtz:.2f}""") + self.dtz = self.__d.bl.dtz_min else: self.dtz_label.setText(f"{self.dtz:.2f}")