From 043a5eef455dfdb491c1d9aa725ce56b67aaf9d2 Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Sun, 29 Jun 2025 11:10:50 +0200 Subject: [PATCH] GUI: Manual sample panel (WIP) --- daq/src/aaredaq/daq.py | 12 ++ daq/src/aaredaq/devices.py | 10 +- daq/src/aaredaq/server.py | 6 + gui/src/aaregui/main_window.py | 18 +++ gui/src/aaregui/panels/manual_sample_panel.py | 116 +++++++++++++++--- gui/src/aaregui/panels/tell_sample_panel.py | 11 +- 6 files changed, 152 insertions(+), 21 deletions(-) diff --git a/daq/src/aaredaq/daq.py b/daq/src/aaredaq/daq.py index 90cd6246..b00a2013 100644 --- a/daq/src/aaredaq/daq.py +++ b/daq/src/aaredaq/daq.py @@ -177,6 +177,11 @@ class AareDAQ: raise def create_sample(self, target: SampleShortInfo): + curr_sample = self.__cfg.current_sample + + if curr_sample is not None and curr_sample.location is not None: + raise Exception("Sample from TELL is loaded") + self.__aare.create_manual_sample(target) self.__cfg.current_sample = target @@ -839,3 +844,10 @@ class AareDAQ: if self.__cfg.state == BeamlineStateEnum.DataCollection: self.__devs.aerotech.stop() self.__jfjoch.cancel() + + def anneal(self, time_s: float): + self.__cfg.set_busy(BeamlineStateEnum.SampleAlignment) + try: + self.__devs.anneal(time_s) + finally: + self.__cfg.state_busy = False diff --git a/daq/src/aaredaq/devices.py b/daq/src/aaredaq/devices.py index 34091480..9b1717f6 100644 --- a/daq/src/aaredaq/devices.py +++ b/daq/src/aaredaq/devices.py @@ -1,3 +1,5 @@ +import time + from epics import PV from aaredaqlib.beamline import MXBeamline @@ -47,6 +49,7 @@ class BeamlineDevices: self.bsy = MyMotor(f"{BEAMLINE}-ES-BS:TRY1") self.__bpm = PV(f"{BEAMLINE}-OP-XBPM1:SumAll:MeanValue_RBV") + self.__anneal = PV(f"{BEAMLINE}-ES-ANN:SET_POS") self.aerotech = aerotech.Abr(beamline) @@ -339,4 +342,9 @@ class BeamlineDevices: # 2 nA -> 4e11 ph/s if self.transmission.get() is None: return 0.0 - return self.transmission.get() * self.__bpm.value / 2.0 * 4e11 \ No newline at end of file + return self.transmission.get() * self.__bpm.value / 2.0 * 4e11 + + def anneal(self, value: float): + self.__anneal.put(1) + time.sleep(value) + self.__anneal.put(0) diff --git a/daq/src/aaredaq/server.py b/daq/src/aaredaq/server.py index ecc176b3..cf83b7c5 100644 --- a/daq/src/aaredaq/server.py +++ b/daq/src/aaredaq/server.py @@ -98,6 +98,12 @@ async def save_abr_meas_pos(token: str = Depends(oauth2_scheme)): daq.save_abr_meas_pos() return "OK" +@app.post("/beamline/anneal") +async def anneal(time_s: float, token: str = Depends(oauth2_scheme)): + auth.check_jwt_rw(cfg, auth.parse_token(token)) + daq.anneal(time_s) + return "OK" + @app.post("/beamline/goto_abr_meas_pos") async def goto_abr_meas_pos(token: str = Depends(oauth2_scheme)): auth.check_jwt_rw(cfg, auth.parse_token(token)) diff --git a/gui/src/aaregui/main_window.py b/gui/src/aaregui/main_window.py index c68e0cdf..f81ecf1f 100644 --- a/gui/src/aaregui/main_window.py +++ b/gui/src/aaregui/main_window.py @@ -18,6 +18,7 @@ from aaredaqlib.sample_geometry import SampleGeometryModel from aaregui.panels.beamline_controls import BeamlineControls from aaregui.panels.data_collection_settings import DataCollectionSettings +from aaregui.panels.manual_sample_panel import ManualSamplePanel from aaregui.panels.sample_queue_panel import SampleQueuePanel from aaregui.panels.tell_sample_panel import TellSamplePanel from aaregui.scan_logic.raster_grid_manager import RasterGridManager @@ -121,6 +122,13 @@ class MainWindow(QMainWindow): self.job_list_dock.setAllowedAreas(Qt.DockWidgetArea.BottomDockWidgetArea) self.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, self.job_list_dock) + self.manual_sample_panel = ManualSamplePanel() + + self.manual_sample_dock = QDockWidget("Manual sample", self) + self.manual_sample_dock.setWidget(self.manual_sample_panel) + self.manual_sample_dock.setAllowedAreas(Qt.DockWidgetArea.BottomDockWidgetArea) + self.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, self.manual_sample_dock) + self.setCentralWidget(top_widget) self.setWindowTitle("AareGUI") @@ -228,11 +236,14 @@ class MainWindow(QMainWindow): self.daq.update.connect(self.beamline.zoom_panel.update_daq_status) self.daq.update.connect(self.beamline.beam_mark.update_daq_status) self.daq.update.connect(self.sample_logic.update_daq_status) + self.daq.update.connect(self.manual_sample_panel.update_daq_status) self.daq.standard_scan_completed.connect(self.rotation.scan_completed) self.daq.automated_scan_done.connect(self.job_list_panel.automated_scan_done) self.daq.raster_scan_completed.connect(self.raster.grid_scan_completed) + self.manual_sample_panel.sample_manual.connect(self.daq.sample_manual) + def create_menu_bar(self): """Create a menu bar with File->Quit and Help->About.""" # Main menu bar @@ -262,6 +273,13 @@ class MainWindow(QMainWindow): show_job_list_action.triggered.connect(lambda: self.job_list_dock.setVisible(show_job_list_action.isChecked())) view_menu.addAction(show_job_list_action) + show_manual_sample_action = QAction("Show manual sample", self) + show_manual_sample_action.setCheckable(True) + show_manual_sample_action.setChecked(True) + show_manual_sample_action.triggered.connect(self.manual_sample_dock.setVisible) + # When dock visibility changes, update the action's checked state + show_manual_sample_action.triggered.connect(lambda: self.manual_sample_dock.setVisible(show_job_list_action.isChecked())) + view_menu.addAction(show_manual_sample_action) # Help menu help_menu = menu_bar.addMenu("Help") about_action = QAction("About", self) # Action for 'About' diff --git a/gui/src/aaregui/panels/manual_sample_panel.py b/gui/src/aaregui/panels/manual_sample_panel.py index 8ba9a9e0..9693cdf2 100644 --- a/gui/src/aaregui/panels/manual_sample_panel.py +++ b/gui/src/aaregui/panels/manual_sample_panel.py @@ -1,41 +1,123 @@ from PySide6.QtCore import Signal, Slot -from PySide6.QtWidgets import QWidget, QGridLayout, QLabel, QTextEdit, QPushButton +from PySide6.QtWidgets import QWidget, QGridLayout, QLabel, QTextEdit, QPushButton, QCheckBox, QLineEdit +from aareDBclient import DataCollectionParameters -from aaredaqlib.models import SampleShortInfo +from aaredaqlib.models import SampleShortInfo, DAQStatusModel +from aaregui.widgets.number_line_edit import NumberLineEdit from aaregui.widgets.title_label import TitleLabel -class OmegaPanel(QWidget): +class ManualSamplePanel(QWidget): sample_manual = Signal(SampleShortInfo) sample_clear = Signal() def __init__(self, parent=None): super().__init__(parent) - self.__sample = SampleShortInfo( - db_id = -1, - puck_name="", - dewar_name="", - sample_name="Manual", - pin=0 - ) + self.__sample_name = "Lyso" + + self.__pgroup = "p16371" grid_layout = QGridLayout(self) - grid_layout.addWidget(TitleLabel("Manual sample", self), 0, 0, 1, 2) + grid_layout.addWidget(TitleLabel("Manual sample", self), 0, 0, 1, 3) - grid_layout.addWidget(QLabel("Name"), 1, 0) - self._text_name = QTextEdit(self.__sample.sample_name) + grid_layout.addWidget(QLabel("Sample"), 1, 0) + self._text_name = QLineEdit(self.__sample_name) grid_layout.addWidget(self._text_name, 1, 1) self._text_name.textChanged.connect(self._name_changed) + self._text_name.setStyleSheet("background-color: rgb(255, 255, 255);") + + # Unit cell parameters + self._unit_cell = QCheckBox("Provide unit cell") + grid_layout.addWidget(self._unit_cell, 2, 1) + + # a, b, c parameters (in Ångströms) + grid_layout.addWidget(QLabel("a"), 3, 0) + self._text_a = NumberLineEdit(5, 1000, 39, parent=self) + grid_layout.addWidget(self._text_a, 3, 1) + grid_layout.addWidget(QLabel("Å"), 3, 2) + + grid_layout.addWidget(QLabel("b"), 4, 0) + self._text_b = NumberLineEdit(5, 1000, 78, parent=self) + grid_layout.addWidget(self._text_b, 4, 1) + grid_layout.addWidget(QLabel("Å"), 4, 2) + + grid_layout.addWidget(QLabel("c"), 5, 0) + self._text_c = NumberLineEdit(5, 1000, 78, parent=self) + grid_layout.addWidget(self._text_c, 5, 1) + grid_layout.addWidget(QLabel("Å"), 5, 2) + + # alpha, beta, gamma parameters (in degrees) + grid_layout.addWidget(QLabel("α"), 6, 0) + self._text_alpha = NumberLineEdit(30, 330, 90, parent=self) + grid_layout.addWidget(self._text_alpha, 6, 1) + grid_layout.addWidget(QLabel("°"), 6, 2) + + grid_layout.addWidget(QLabel("β"), 7, 0) + self._text_beta = NumberLineEdit(30, 330, 90, parent=self) + grid_layout.addWidget(self._text_beta, 7, 1) + grid_layout.addWidget(QLabel("°"), 7, 2) + + grid_layout.addWidget(QLabel("γ"), 8, 0) + self._text_gamma = NumberLineEdit(30, 330, 90, parent=self) + grid_layout.addWidget(self._text_gamma, 8, 1) + grid_layout.addWidget(QLabel("°"), 8, 2) + + grid_layout.addWidget(QLabel("SG"), 9, 0) + self._text_sg = NumberLineEdit(1, 250, 1, 0, parent=self) + grid_layout.addWidget(self._text_sg, 9, 1) + self._add_button = QPushButton("Add") - grid_layout.addWidget(self._add_button, 2, 0, 1 ,2) + grid_layout.addWidget(self._add_button, 10, 0, 1, 3) self._add_button.clicked.connect(self._add_clicked) + self._unit_cell.toggled.connect(self._unit_cell_toggled) + self._unit_cell_toggled(False) + + @Slot(bool) + def _unit_cell_toggled(self, checked: bool): + if checked: + self._text_a.setEnabled(True) + self._text_b.setEnabled(True) + self._text_c.setEnabled(True) + self._text_alpha.setEnabled(True) + self._text_beta.setEnabled(True) + self._text_gamma.setEnabled(True) + self._text_sg.setEnabled(True) + else: + self._text_a.setEnabled(False) + self._text_b.setEnabled(False) + self._text_c.setEnabled(False) + self._text_alpha.setEnabled(False) + self._text_beta.setEnabled(False) + self._text_gamma.setEnabled(False) + self._text_sg.setEnabled(False) + @Slot(str) - def _name_vhanged(self, s: str): - self.__sample.sample_name = s + def _name_changed(self, s: str): + self.__sample_name = s @Slot() def _add_clicked(self): - self.sample_manual.emit(self.__sample) + data_processing = None + if self._unit_cell.isChecked(): + data_processing = DataCollectionParameters( + spacegroupnumber=int(self._text_sg.value), + cellparameters=f"{self._text_a.value:.2f} {self._text_b.value:.2f} {self._text_c.value:.2f} {self._text_alpha.value:.2f} {self._text_beta.value:.2f} {self._text_gamma.value:.2f}" + ) + + s = SampleShortInfo( + db_id=-1, + puck_name="", + dewar_name="", + sample_name=self.__sample_name, + pin=1, + aaredb_params=data_processing, + user=self.__pgroup, + ) + self.sample_manual.emit(s) + + @Slot(DAQStatusModel) + def update_daq_status(self, s: DAQStatusModel): + self.__pgroup = s.session.current_pgroup diff --git a/gui/src/aaregui/panels/tell_sample_panel.py b/gui/src/aaregui/panels/tell_sample_panel.py index dbc81d9c..2254f9db 100644 --- a/gui/src/aaregui/panels/tell_sample_panel.py +++ b/gui/src/aaregui/panels/tell_sample_panel.py @@ -111,9 +111,14 @@ class TellSamplePanel(QFrame): self.table_model.updateCurrentSample(current_puck=None, current_sample=None) else: try: - self.curr_sample_label.setText( - f"Current sample: {sample.sample_name} ({sample.location.segment}{sample.location.pos}-{sample.pin})" - ) + if sample.location is None: + self.curr_sample_label.setText( + f"Current sample: {sample.sample_name} (Manual mount)" + ) + else: + self.curr_sample_label.setText( + f"Current sample: {sample.sample_name} ({sample.location.segment}{sample.location.pos}-{sample.pin})" + ) self.table_model.updateCurrentSample( current_puck=sample.puck_name, current_sample=sample.db_id )