diff --git a/common/src/aaredaqlib/models.py b/common/src/aaredaqlib/models.py index 8de8c8b2..c0afd544 100644 --- a/common/src/aaredaqlib/models.py +++ b/common/src/aaredaqlib/models.py @@ -429,6 +429,14 @@ class BeamMarkCoeffModel(BaseModel): y=self.coeff_y[0] * zoom**2 + self.coeff_y[1] * zoom + self.coeff_y[2], ) +class FluorescenceSpectrumParameterModel(BaseModel): + acq_time_s: float + +class FluorescenceSpectrumOutputModel(BaseModel): + bkg: list[float] | None = None + spectrum: list[float] + energy_eV: list[float] + class MLBoxType(Enum): Loop_all = 0 Pin = 1 diff --git a/daq/src/aaredaq/daq.py b/daq/src/aaredaq/daq.py index cac4ba14..61e22e68 100644 --- a/daq/src/aaredaq/daq.py +++ b/daq/src/aaredaq/daq.py @@ -3,6 +3,7 @@ import json import math import statistics import time +from asyncio import sleep from datetime import datetime from math import ceil from typing import List, Tuple, Optional, Callable, Dict @@ -34,7 +35,8 @@ from aaredaqlib.models import ( PuckLoadedInfo, SampleShortInfoList, DAQStatusModel, BeamlineStatus, SessionStatus, SampleCameraSettings, AutofocusSettings, BoundingBoxModel, - ZoomModeEnum, CrystalSize, SimpleScanParameters, MLBoxModel) + ZoomModeEnum, CrystalSize, SimpleScanParameters, MLBoxModel, FluorescenceSpectrumParameterModel, + FluorescenceSpectrumOutputModel) from aaredaqlib.raster_grid import RasterGridRequest, CompletedRasterGrid, CompletedRasterGridElem from aaredaqlib.rotation_scan import RotationScanRequest, CompletedRotationScan from aaredaqlib.sample_geometry import SampleGeometryModel @@ -1525,11 +1527,31 @@ class AareDAQ: finally: self.__cfg.state_busy = False + def fluorimeter_take_spectrum(self, fm: FluorescenceSpectrumParameterModel) -> FluorescenceSpectrumOutputModel: + self.__cfg.set_busy(BeamlineStateEnum.SampleAlignment) + try: + self.__devs.fluorimeter.start_acquisition(erase=True) + self.__devs.aerotech.set_shutter(1) + sleep(fm.acq_time_s) + self.__devs.aerotech.set_shutter(0) + self.__devs.fluorimeter.stop_acquisition() + spectrum = self.__devs.fluorimeter.get_current_data() + energy = [1.0 * i + 0.0 for i in range(len(spectrum))] + self.__cfg.state_busy = False + return FluorescenceSpectrumOutputModel(spectrum=spectrum, + bkg=self.__devs.fluorimeter.get_current_background(), + energy_eV=energy) + except Exception as e: + self.__cfg.state_busy = False + raise e + def fluorimeter_start(self, erase: bool = False): + self.__devs.aerotech.set_shutter(1) self.__devs.fluorimeter.start_acquisition(erase=erase) def fluorimeter_stop(self): self.__devs.fluorimeter.stop_acquisition() + self.__devs.aerotech.set_shutter(0) def fluorimeter_status(self) -> int | None: return self.__devs.fluorimeter.check_status() diff --git a/daq/src/aaredaq/server.py b/daq/src/aaredaq/server.py index c6c5e54d..4130c64a 100644 --- a/daq/src/aaredaq/server.py +++ b/daq/src/aaredaq/server.py @@ -10,7 +10,8 @@ from aaredaqlib.coordinate import SmargonCoordinate, Coordinate from aaredaqlib.logger_config import setup_logger from aaredaqlib.models import SampleShortInfo, DAQStatusModel, BeamlineStateEnum, BeamlineSettingsModel, \ SampleShortInfoList, SessionStatus, SampleCameraSettings, AutofocusSettings, TokenData, \ - CryojetSettingsModel, SimpleScanParameters, CrystalSize + CryojetSettingsModel, SimpleScanParameters, CrystalSize, FluorescenceSpectrumParameterModel, \ + FluorescenceSpectrumOutputModel from aaredaqlib.raster_grid import RasterGridRequest, CompletedRasterGrid from aaredaqlib.rotation_scan import RotationScanRequest, CompletedRotationScan from aaredaqlib.sample_geometry import SampleGeometryModel @@ -546,10 +547,15 @@ async def get_all_pgroups(token: str = Depends(oauth2_scheme)): logger.error(f"Failed to list pgroups: {e}") return [] +@app.post("/fluorimeter/spectrum") +async def fluorimeter_spectrum(input: FluorescenceSpectrumParameterModel, token: str = Depends(oauth2_scheme)) -> FluorescenceSpectrumOutputModel: + auth.check_jwt_rw(cfg, auth.parse_token(token)) + return daq.fluorimeter_take_spectrum(input) + @app.post("/fluorimeter/start") async def fluorimeter_start(erase: bool = False, token: str = Depends(oauth2_scheme)) -> str: auth.check_jwt_rw(cfg, auth.parse_token(token)) - daq.fluorimeter_start(erase=erase) + daq.fluorimeter_start(erase) return "OK" @app.post("/fluorimeter/stop") diff --git a/gui/src/aaregui/main_window.py b/gui/src/aaregui/main_window.py index 6e123126..f52bffd1 100644 --- a/gui/src/aaregui/main_window.py +++ b/gui/src/aaregui/main_window.py @@ -356,13 +356,8 @@ class MainWindow(QMainWindow): self.status_bar.get_all_pgroups.connect(self.daq.get_all_pgroups) self.daq.staff_pgroups_loaded.connect(self.status_bar.staff_pgroups_loaded) - self.fluor_panel.start.connect(self.daq.fluorimeter_start) - self.fluor_panel.start_erase.connect(self.daq.fluorimeter_start_erase) - self.fluor_panel.stop.connect(self.daq.fluorimeter_stop) - self.fluor_panel.snapshot.connect(self.daq.fluorimeter_request_snapshot) - self.fluor_panel.live.connect(self.daq.fluorimeter_start_erase) - self.fluor_panel.live.connect(self.daq.start_fluorimeter_stream) - self.daq.fluorimeter_update.connect(self.fluor_panel.update_plot) + self.fluor_panel.spectrum.connect(self.daq.fluorimeter_spectrum) + self.daq.fluorimeter_spectrum_update.connect(self.fluor_panel.update_plot) def create_menu_bar(self): """Create a menu bar with File->Quit and Help->About.""" diff --git a/gui/src/aaregui/panels/fluoresence_panel.py b/gui/src/aaregui/panels/fluoresence_panel.py index 2c263bbb..9bbe8274 100644 --- a/gui/src/aaregui/panels/fluoresence_panel.py +++ b/gui/src/aaregui/panels/fluoresence_panel.py @@ -4,6 +4,7 @@ from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.figure import Figure import numpy as np +from aaredaqlib.models import FluorescenceSpectrumParameterModel, FluorescenceSpectrumOutputModel from aaregui.widgets.number_line_edit import NumberLineEdit from aaregui.widgets.title_label import TitleLabel from aaredaqlib.logger_config import setup_logger @@ -11,59 +12,36 @@ from aaredaqlib.logger_config import setup_logger logger = setup_logger(__name__) class FluorescencePanel(QWidget): - - start = Signal() - start_erase = Signal() - stop = Signal() - snapshot = Signal() - live = Signal() + spectrum = Signal(FluorescenceSpectrumParameterModel) def __init__(self, parent=None): super().__init__(parent) grid = QGridLayout(self) - grid.addWidget(TitleLabel("Fluorescence", self), 0, 0, 1, 4) + grid.addWidget(TitleLabel("Fluorescence", self), 0, 0) - self.btn_start = QPushButton("Start") - self.btn_start_erase = QPushButton("Erase+Start") - self.btn_stop = QPushButton("Stop") - self.btn_live = QPushButton("Start Live") - self.btn_snapshot = QPushButton("Snapshot") - for i, b in enumerate((self.btn_start, self.btn_start_erase, self.btn_stop, self.btn_live, self.btn_snapshot), start=1): - grid.addWidget(b, 1, i - 1) + self.btn_start = QPushButton("Spectrum") + grid.addWidget(self.btn_start, 1, 0) self.fig = Figure(figsize=(5, 3), tight_layout=True) self.ax = self.fig.add_subplot(111) self.canvas = FigureCanvas(self.fig) - grid.addWidget(self.canvas, 2, 0, 1, 4) + grid.addWidget(self.canvas, 2, 0) # Wire buttons - self.btn_start.clicked.connect(self.start.emit) - self.btn_start_erase.clicked.connect(self.start_erase.emit) - self.btn_stop.clicked.connect(self.stop.emit) - self.btn_snapshot.clicked.connect(self.snapshot.emit) - self.btn_live.clicked.connect(self.live.emit) + self.btn_start.clicked.connect(lambda: self.spectrum.emit(FluorescenceSpectrumParameterModel(acq_time_s=0.1))) - - @Slot(list, list, int) - def update_plot(self, data, background, status): + @Slot(FluorescenceSpectrumOutputModel) + def update_plot(self, f: FluorescenceSpectrumOutputModel): try: self.ax.clear() - - if data: - y = np.array(data, dtype=float) - x = np.arange(len(y), dtype=int) + if len(f.spectrum) > 0 and len(f.spectrum) == len(f.energy_eV): + x = np.array(f.energy_eV, dtype=float) + y = np.array(f.spectrum, dtype=float) self.ax.plot(x, y, lw=1.0, label="Data") - if background: - b = np.array(background, dtype=float) - xb = np.arange(len(b), dtype=int) - self.ax.plot(xb, b, lw=1.0, label="Background") - - self.ax.set_xlabel("Channel") + self.ax.set_xlabel("Energy eV") self.ax.set_ylabel("Counts") - if self.ax.has_data(): - self.ax.legend(loc="best") self.ax.grid(True, alpha=0.3) self.canvas.draw_idle() except Exception as e: diff --git a/gui/src/aaregui/threads/daq_worker.py b/gui/src/aaregui/threads/daq_worker.py index f40130a4..6d3ac630 100644 --- a/gui/src/aaregui/threads/daq_worker.py +++ b/gui/src/aaregui/threads/daq_worker.py @@ -9,7 +9,7 @@ from jfjoch_client import ScanResult, ScanResultImagesInner from aaredaqlib.coordinate import SmargonCoordinate, Coordinate from aaredaqlib.models import DAQStatusModel, SampleShortInfoList, SampleShortInfo, SampleCameraSettings, \ - AutofocusSettings, SimpleScanParameters + AutofocusSettings, SimpleScanParameters, FluorescenceSpectrumParameterModel, FluorescenceSpectrumOutputModel from aaredaqlib.raster_grid import RasterGridRequest, CompletedRasterGrid from aaredaqlib.rotation_scan import RotationScanRequest, CompletedRotationScan @@ -34,6 +34,7 @@ class DAQWorker(QObject): face_detection_result = Signal(dict) staff_pgroups_loaded = Signal(list) fluorimeter_update = Signal(list, list, int) + fluorimeter_spectrum_update = Signal(FluorescenceSpectrumOutputModel) def __init__(self, base_url: str | None, token: str, parent=None): super().__init__(parent) @@ -560,6 +561,26 @@ class DAQWorker(QObject): logger.error(f"Fluorimeter background error: {e}") self.http_error.emit(str(e)) + @Slot() + def fluorimeter_spectrum(self, f: FluorescenceSpectrumParameterModel): + if self.__base_url is None: + return + req = QNetworkRequest(QUrl(f"{self.__base_url}/fluorimeter/spectrum")) + req.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8")) + req.setRawHeader(b"Content-Type", b"application/json") + body = f.model_dump_json() + reply = self.__net_manager.post(req, QByteArray(body.encode("utf-8"))) + reply.finished.connect(lambda: self._handle_fluorimeter_spectrum(reply)) + + def _handle_fluorimeter_spectrum(self, reply: QNetworkReply): + try: + response_data = self.handle_response(reply) + parsed_response = FluorescenceSpectrumOutputModel.model_validate_json(response_data) + self.fluorimeter_spectrum_update.emit(parsed_response) + except Exception as e: + logger.error(f"Exception from fluorimeter spectrum: {e}") + self.http_error.emit(str(e)) + @Slot() def fluorimeter_request_snapshot(self): if self.__base_url is None: