Beamline dispatch feature and tidy-up #120
@@ -1,9 +1,15 @@
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -42,6 +48,63 @@ jobs:
|
||||
index_username: ${{ secrets.GITEA_USER }}
|
||||
index_password: ${{ secrets.GITEA_TOKEN }}
|
||||
|
||||
- name: Run Pytest
|
||||
env:
|
||||
QT_QPA_PLATFORM: offscreen
|
||||
BEAMLINE: SIMULATED
|
||||
run: |
|
||||
source .venv/bin/activate
|
||||
pytest ./tests/unit
|
||||
|
||||
test-with-beamline-plugins:
|
||||
runs-on: ubuntu-latest
|
||||
needs: ["lint", "test"]
|
||||
strategy:
|
||||
matrix:
|
||||
plugin_repo: ["pxi_bec", "pxii_bec", "pxiii_bec"]
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup
|
||||
uses: ./.gitea/actions/install
|
||||
with:
|
||||
python_version: "3.12"
|
||||
index_username: ${{ secrets.GITEA_USER }}
|
||||
index_password: ${{ secrets.GITEA_TOKEN }}
|
||||
|
||||
- name: Install beamline plugin
|
||||
run: |
|
||||
source .venv/bin/activate
|
||||
git clone https://gitea.psi.ch/bec/mx_bec.git
|
||||
git clone https://gitea.psi.ch/bec/${{ matrix.plugin_repo }}.git
|
||||
uv pip install -e ./mx_bec
|
||||
uv pip install -e ./${{ matrix.plugin_repo }}
|
||||
|
||||
- name: Run Pytest
|
||||
env:
|
||||
QT_QPA_PLATFORM: offscreen
|
||||
BEAMLINE: SIMULATED
|
||||
run: |
|
||||
source .venv/bin/activate
|
||||
pytest ./tests/unit
|
||||
|
||||
test-with-coverage:
|
||||
runs-on: ubuntu-latest
|
||||
needs: ["lint", "test", "test-with-beamline-plugins"]
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup
|
||||
uses: ./.gitea/actions/install
|
||||
with:
|
||||
python_version: "3.12"
|
||||
index_username: ${{ secrets.GITEA_USER }}
|
||||
index_password: ${{ secrets.GITEA_TOKEN }}
|
||||
|
||||
- name: Run Pytest with Coverage
|
||||
env:
|
||||
QT_QPA_PLATFORM: offscreen
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
import json
|
||||
import threading
|
||||
import time
|
||||
from typing import Any, Dict
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import zmq
|
||||
import time
|
||||
import threading
|
||||
from typing import Dict, Any
|
||||
|
||||
|
||||
class ImageStatsReceiver:
|
||||
def __init__(self, zmq_url: str):
|
||||
"""Initialize ZeroMQ receiver for image statistics calculation"""
|
||||
context = zmq.Context()
|
||||
self.__socket = context.socket(zmq.SUB)
|
||||
self.__socket.setsockopt(zmq.SUBSCRIBE, b"")
|
||||
self.__socket.setsockopt(zmq.RCVTIMEO, 1000) # 1 second timeout
|
||||
self.__socket.setsockopt(zmq.LINGER, 0) # Don't linger on close
|
||||
self._socket = context.socket(zmq.SUB)
|
||||
self._socket.setsockopt(zmq.SUBSCRIBE, b"")
|
||||
self._socket.setsockopt(zmq.RCVTIMEO, 1000) # 1 second timeout
|
||||
self._socket.setsockopt(zmq.LINGER, 0) # Don't linger on close
|
||||
|
||||
try:
|
||||
self.__socket.connect(zmq_url)
|
||||
self._socket.connect(zmq_url)
|
||||
print(f"Connected to ZeroMQ socket: {zmq_url}")
|
||||
except Exception as e:
|
||||
print(f"Failed to connect to {zmq_url}: {e}")
|
||||
@@ -187,7 +188,7 @@ class ImageStatsReceiver:
|
||||
def process_message(self):
|
||||
"""Process a single ZeroMQ message"""
|
||||
try:
|
||||
r = self.__socket.recv_multipart(zmq.NOBLOCK)
|
||||
r = self._socket.recv_multipart(zmq.NOBLOCK)
|
||||
if len(r) != 2:
|
||||
return None
|
||||
|
||||
@@ -373,8 +374,8 @@ class ImageStatsReceiver:
|
||||
def stop(self):
|
||||
"""Stop the receiver and all threads"""
|
||||
self.running = False
|
||||
if self.__socket:
|
||||
self.__socket.close()
|
||||
if self._socket:
|
||||
self._socket.close()
|
||||
print("Image stats receiver stopped")
|
||||
|
||||
|
||||
@@ -415,16 +416,6 @@ def get_latest_image_stats():
|
||||
return None
|
||||
|
||||
|
||||
def get_latest_image_stats():
|
||||
"""Get the latest image statistics including radial profile"""
|
||||
global stats_receiver
|
||||
|
||||
if stats_receiver and stats_receiver.latest_stats:
|
||||
with stats_receiver.stats_lock:
|
||||
return stats_receiver.latest_stats.copy()
|
||||
return None
|
||||
|
||||
|
||||
def get_radial_profile():
|
||||
"""Get just the radial profile data"""
|
||||
global stats_receiver
|
||||
@@ -441,31 +432,3 @@ def save_current_radial_profile(filename: str = None):
|
||||
if stats_receiver:
|
||||
return stats_receiver.save_radial_profile_to_file(filename)
|
||||
return None
|
||||
|
||||
|
||||
# # Auto-start when daq.py is imported/run
|
||||
# if __name__ == "__main__":
|
||||
# # If running daq.py directly
|
||||
# start_image_stats_receiver()
|
||||
#
|
||||
# try:
|
||||
# # Your existing daq.py code continues here
|
||||
# print("DAQ system running with image statistics monitoring...")
|
||||
#
|
||||
# # Keep the program running
|
||||
# while True:
|
||||
# time.sleep(1)
|
||||
#
|
||||
# # Optional: Access latest stats in your main code
|
||||
# latest = get_latest_image_stats()
|
||||
# if latest and latest['message_count'] % 10 == 0: # Every 10th message
|
||||
# print(f"Main thread sees: Mean={latest['mean']:.1f}")
|
||||
#
|
||||
# except KeyboardInterrupt:
|
||||
# print("\nShutting down...")
|
||||
# finally:
|
||||
# stop_image_stats_receiver()
|
||||
#
|
||||
# else:
|
||||
# # If daq.py is imported as a module, auto-start the receiver
|
||||
# start_image_stats_receiver()
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
"""A single API to dispatch functions based on the current beamline"""
|
||||
@@ -0,0 +1,24 @@
|
||||
from aarecommon.config.beamline import MXBeamline, mx_beamline
|
||||
|
||||
from aare.beamline_dispatch.protocols import BeamlineDispatch
|
||||
|
||||
|
||||
def get_beamline_dispatch() -> BeamlineDispatch:
|
||||
"""Fetch the dispatch object for the beamline detected from the BEAMLINE environment variable."""
|
||||
match mx_beamline():
|
||||
case MXBeamline.SIMULATED:
|
||||
from .simulated import SimulatedDispatch
|
||||
|
||||
return SimulatedDispatch()
|
||||
case MXBeamline.X06DA:
|
||||
from .x06da import X06daDispatch
|
||||
|
||||
return X06daDispatch()
|
||||
case MXBeamline.X06SA:
|
||||
from .x06sa import X06saDispatch
|
||||
|
||||
return X06saDispatch()
|
||||
case MXBeamline.X10SA:
|
||||
from .x10sa import X10saDispatch
|
||||
|
||||
return X10saDispatch()
|
||||
@@ -0,0 +1,3 @@
|
||||
from .beamline_dispatch import DefaultDispatch
|
||||
|
||||
__all__ = ["DefaultDispatch"]
|
||||
@@ -0,0 +1,24 @@
|
||||
import os
|
||||
|
||||
from aare.beamline_dispatch.protocols import AuthDispatch, BeamlineDispatch
|
||||
|
||||
|
||||
class DefaultAuthDispatch(AuthDispatch):
|
||||
def get_jwt_key(self) -> str:
|
||||
if (key := os.environ.get("JWT_AAREDAQ_KEY")) is None:
|
||||
raise Exception(
|
||||
"JWT_AAREDAQ_KEY environment variable not set, cannot guarantee safe authentication."
|
||||
)
|
||||
return key
|
||||
|
||||
|
||||
class DefaultDispatch(BeamlineDispatch):
|
||||
"""Default implementation for anything which can vary between beamlines and/or simulation.
|
||||
Should be safe and fail rather than assuming anything."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._auth = DefaultAuthDispatch()
|
||||
|
||||
@property
|
||||
def auth(self):
|
||||
return self._auth
|
||||
@@ -0,0 +1,37 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Any
|
||||
|
||||
|
||||
class AuthDispatch(ABC):
|
||||
@abstractmethod
|
||||
def get_jwt_key(self) -> str: ...
|
||||
|
||||
|
||||
class BecMacros(ABC):
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def save_and_reload() -> tuple[Any, Any]: ...
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def save_current_position(device, position, axis=None, force=False, max_delta=0.5) -> None: ...
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def init_beamline_environment() -> tuple[Any, Any]: ...
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def bl_energy(energy_ev, move_gap=True, mono_scan=True, plot=True): ...
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def get_current_energy(): ...
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def mono_pitch_scan(plot=True): ...
|
||||
|
||||
|
||||
class BeamlineDispatch(ABC):
|
||||
@property
|
||||
@abstractmethod
|
||||
def auth(self) -> AuthDispatch: ...
|
||||
@property
|
||||
@abstractmethod
|
||||
def bec_macros(self) -> BecMacros: ...
|
||||
@@ -0,0 +1,3 @@
|
||||
from .beamline_dispatch import SimulatedDispatch
|
||||
|
||||
__all__ = ["SimulatedDispatch"]
|
||||
@@ -0,0 +1,38 @@
|
||||
from typing import Any
|
||||
|
||||
from aare.beamline_dispatch.default.beamline_dispatch import DefaultDispatch
|
||||
from aare.beamline_dispatch.protocols import AuthDispatch, BecMacros
|
||||
|
||||
|
||||
class SimulatedAuthDispatch(AuthDispatch):
|
||||
def get_jwt_key(self) -> str:
|
||||
return "Ns93ijN8VHv4ybvXaGNEDKUb3Sif4m4MYpfcEBIcs1h"
|
||||
|
||||
|
||||
class SimulatedBecMacros(BecMacros):
|
||||
@staticmethod
|
||||
def save_and_reload() -> tuple[Any, Any]:
|
||||
return (None, None)
|
||||
|
||||
@staticmethod
|
||||
def save_current_position(device, position, axis=None, force=False, max_delta=0.5) -> None: ...
|
||||
@staticmethod
|
||||
def init_beamline_environment() -> tuple[Any, Any]:
|
||||
return (None, None)
|
||||
|
||||
@staticmethod
|
||||
def bl_energy(energy_ev, move_gap=True, mono_scan=True, plot=True): ...
|
||||
@staticmethod
|
||||
def get_current_energy(): ...
|
||||
@staticmethod
|
||||
def mono_pitch_scan(plot=True): ...
|
||||
|
||||
|
||||
class SimulatedDispatch(DefaultDispatch):
|
||||
def __init__(self) -> None:
|
||||
self._auth = SimulatedAuthDispatch()
|
||||
self._bec_macros = SimulatedBecMacros()
|
||||
|
||||
@property
|
||||
def bec_macros(self):
|
||||
return self._bec_macros
|
||||
@@ -0,0 +1,3 @@
|
||||
from .beamline_dispatch import X06daDispatch
|
||||
|
||||
__all__ = ["X06daDispatch"]
|
||||
@@ -0,0 +1,4 @@
|
||||
from aare.beamline_dispatch.default.beamline_dispatch import DefaultDispatch
|
||||
|
||||
|
||||
class X06daDispatch(DefaultDispatch): ...
|
||||
@@ -0,0 +1,3 @@
|
||||
from .beamline_dispatch import X06saDispatch
|
||||
|
||||
__all__ = ["X06saDispatch"]
|
||||
@@ -0,0 +1,4 @@
|
||||
from aare.beamline_dispatch.default.beamline_dispatch import DefaultDispatch
|
||||
|
||||
|
||||
class X06saDispatch(DefaultDispatch): ...
|
||||
@@ -0,0 +1,3 @@
|
||||
from .beamline_dispatch import X10saDispatch
|
||||
|
||||
__all__ = ["X10saDispatch"]
|
||||
@@ -0,0 +1,48 @@
|
||||
from typing import Any
|
||||
|
||||
from aare.beamline_dispatch.default.beamline_dispatch import DefaultDispatch
|
||||
from aare.beamline_dispatch.protocols import BecMacros
|
||||
|
||||
|
||||
class X10SaBecMacros(BecMacros):
|
||||
def __init__(self) -> None:
|
||||
from pxii_bec.macros.build_devices import ( # type: ignore
|
||||
save_and_reload,
|
||||
save_current_position,
|
||||
)
|
||||
from pxii_bec.macros.init_beamline import init_beamline_environment # type: ignore
|
||||
from pxii_bec.macros.pxii_energy import ( # type: ignore
|
||||
bl_energy,
|
||||
get_current_energy,
|
||||
mono_pitch_scan,
|
||||
)
|
||||
|
||||
self.save_and_reload = save_and_reload
|
||||
self.save_current_position = save_current_position
|
||||
self.init_beamline_environment = init_beamline_environment
|
||||
self.bl_energy = bl_energy
|
||||
self.get_current_energy = get_current_energy
|
||||
self.mono_pitch_scan = mono_pitch_scan
|
||||
|
||||
@staticmethod
|
||||
def save_and_reload() -> tuple[Any, Any]: ...
|
||||
@staticmethod
|
||||
def save_current_position(device, position, axis=None, force=False, max_delta=0.5) -> None: ...
|
||||
@staticmethod
|
||||
def init_beamline_environment() -> tuple[Any, Any]: ...
|
||||
@staticmethod
|
||||
def bl_energy(energy_ev, move_gap=True, mono_scan=True, plot=True): ...
|
||||
@staticmethod
|
||||
def get_current_energy(): ...
|
||||
@staticmethod
|
||||
def mono_pitch_scan(plot=True): ...
|
||||
|
||||
|
||||
class X10saDispatch(DefaultDispatch):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._bec_macros = X10SaBecMacros()
|
||||
|
||||
@property
|
||||
def bec_macros(self) -> BecMacros:
|
||||
return self._bec_macros
|
||||
+27
-27
@@ -64,16 +64,16 @@ class AareWrapper:
|
||||
|
||||
# Identity Forwarding (Optional now that mTLS is active, but safe to keep)
|
||||
self.client.default_headers["X-Shared-Password"] = os.getenv("AAREDB_SHARED_PASSWORD")
|
||||
self.__host = host
|
||||
self.__tell_api = aareDB.TellsRunnerApi(self.client)
|
||||
self.__sample_api = aareDB.SamplesRunnerApi(self.client)
|
||||
self.__proc_api = aareDB.ProcessingsRunnerApi(self.client)
|
||||
self.__raster_api = aareDB.GridscanRunnerApi(self.client)
|
||||
self.__bl = bl
|
||||
self._host = host
|
||||
self._tell_api = aareDB.TellsRunnerApi(self.client)
|
||||
self._sample_api = aareDB.SamplesRunnerApi(self.client)
|
||||
self._proc_api = aareDB.ProcessingsRunnerApi(self.client)
|
||||
self._raster_api = aareDB.GridscanRunnerApi(self.client)
|
||||
self._bl = bl
|
||||
|
||||
self.__ssl_ca_cert = configuration.ssl_ca_cert
|
||||
self.__cert_file = configuration.cert_file
|
||||
self.__key_file = configuration.key_file
|
||||
self._ssl_ca_cert = configuration.ssl_ca_cert
|
||||
self._cert_file = configuration.cert_file
|
||||
self._key_file = configuration.key_file
|
||||
|
||||
@log_timing(logger, "AareDB call")
|
||||
def set_pucks_beamline(self, input_list: List[PuckLoadedInfo]):
|
||||
@@ -84,8 +84,8 @@ class AareWrapper:
|
||||
puck_name=i.puck_name, segment=i.location.segment, puck_in_segment=i.location.pos
|
||||
)
|
||||
o.append(t)
|
||||
payload = SetTellPositionRequest(pucks=o, tell=self.__bl.value.upper())
|
||||
ret = self.__tell_api.set_tell_positions(set_tell_position_request=payload)
|
||||
payload = SetTellPositionRequest(pucks=o, tell=self._bl.value.upper())
|
||||
ret = self._tell_api.set_tell_positions(set_tell_position_request=payload)
|
||||
logger.debug(ret)
|
||||
|
||||
@log_timing(logger, "AareDB call")
|
||||
@@ -97,7 +97,7 @@ class AareWrapper:
|
||||
)
|
||||
|
||||
try:
|
||||
s.db_id = self.__sample_api.insert_sample(manual_sample).id
|
||||
s.db_id = self._sample_api.insert_sample(manual_sample).id
|
||||
except Exception as e:
|
||||
logger.error(f"Error inserting sample: {e}")
|
||||
|
||||
@@ -114,7 +114,7 @@ class AareWrapper:
|
||||
)
|
||||
return
|
||||
try:
|
||||
self.__sample_api.create_sample_event(
|
||||
self._sample_api.create_sample_event(
|
||||
sample_id=sample_id,
|
||||
sample_event_create=SampleEventCreate(event_type=event_type, comment=comment),
|
||||
)
|
||||
@@ -127,7 +127,7 @@ class AareWrapper:
|
||||
):
|
||||
_, buffer = cv2.imencode(".jpg", bgr_image)
|
||||
jpeg_bytes = io.BytesIO(buffer)
|
||||
url = f"{self.__host}/protected_router/sample_runner/{sample_id}/upload-images"
|
||||
url = f"{self._host}/protected_router/sample_runner/{sample_id}/upload-images"
|
||||
headers = {
|
||||
"accept": "application/json",
|
||||
"X-Shared-Password": os.getenv("AAREDB_SHARED_PASSWORD"),
|
||||
@@ -135,8 +135,8 @@ class AareWrapper:
|
||||
|
||||
request_kwargs = {
|
||||
"files": {"uploaded_file": (filename + ".jpg", jpeg_bytes, "image/jpeg")},
|
||||
"verify": self.__ssl_ca_cert,
|
||||
"cert": (self.__cert_file, self.__key_file),
|
||||
"verify": self._ssl_ca_cert,
|
||||
"cert": (self._cert_file, self._key_file),
|
||||
"headers": headers,
|
||||
}
|
||||
if message is not None:
|
||||
@@ -147,15 +147,15 @@ class AareWrapper:
|
||||
@log_timing(logger, "AareDB call")
|
||||
def upload_jpg(self, sample_id: int, filename: str, jpg_image, message: Optional[str] = None):
|
||||
logger.debug(f"jppg_image of type: {type(jpg_image)}")
|
||||
url = f"{self.__host}/protected_router/sample_runner/{sample_id}/upload-images"
|
||||
url = f"{self._host}/protected_router/sample_runner/{sample_id}/upload-images"
|
||||
headers = {
|
||||
"accept": "application/json",
|
||||
"X-Shared-Password": os.getenv("AAREDB_SHARED_PASSWORD"),
|
||||
}
|
||||
request_kwargs = {
|
||||
"files": {"uploaded_file": (filename + ".jpg", jpg_image, "image/jpeg")},
|
||||
"verify": self.__ssl_ca_cert,
|
||||
"cert": (self.__cert_file, self.__key_file),
|
||||
"verify": self._ssl_ca_cert,
|
||||
"cert": (self._cert_file, self._key_file),
|
||||
"headers": headers,
|
||||
}
|
||||
if message is not None:
|
||||
@@ -229,7 +229,7 @@ class AareWrapper:
|
||||
experiment_params_payload = ExperimentParametersCreate(
|
||||
type="standard", beamline_parameters=beamline_params, sample_id=s.db_id
|
||||
)
|
||||
self.__sample_api.create_experiment_parameters_for_sample(
|
||||
self._sample_api.create_experiment_parameters_for_sample(
|
||||
sample_id=s.db_id, experiment_parameters_create=experiment_params_payload
|
||||
)
|
||||
# logger.debug("Experiment parameters created:", response)
|
||||
@@ -291,7 +291,7 @@ class AareWrapper:
|
||||
experiment_params_payload = ExperimentParametersCreate(
|
||||
type="standard", beamline_parameters=beamline_params, sample_id=s.db_id
|
||||
)
|
||||
self.__sample_api.create_experiment_parameters_for_sample(
|
||||
self._sample_api.create_experiment_parameters_for_sample(
|
||||
sample_id=s.db_id, experiment_parameters_create=experiment_params_payload
|
||||
)
|
||||
# logger.info("Experiment parameters created:", response)
|
||||
@@ -319,7 +319,7 @@ class AareWrapper:
|
||||
return
|
||||
payload = payload_model.model_dump()
|
||||
|
||||
url = f"{self.__host}/protected_router/gridscan_runner/ingest"
|
||||
url = f"{self._host}/protected_router/gridscan_runner/ingest"
|
||||
headers = {
|
||||
"accept": "application/json",
|
||||
"X-Shared-Password": os.getenv("AAREDB_SHARED_PASSWORD"),
|
||||
@@ -330,8 +330,8 @@ class AareWrapper:
|
||||
headers=headers,
|
||||
data=json.dumps(payload),
|
||||
timeout=30,
|
||||
verify=self.__ssl_ca_cert,
|
||||
cert=(self.__cert_file, self.__key_file),
|
||||
verify=self._ssl_ca_cert,
|
||||
cert=(self._cert_file, self._key_file),
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
@@ -417,7 +417,7 @@ class AareWrapper:
|
||||
return
|
||||
payload = payload_model.model_dump()
|
||||
|
||||
url = f"{self.__host}/protected_router/scan_runner/ingest"
|
||||
url = f"{self._host}/protected_router/scan_runner/ingest"
|
||||
headers = {
|
||||
"accept": "application/json",
|
||||
"X-Shared-Password": os.getenv("AAREDB_SHARED_PASSWORD"),
|
||||
@@ -428,8 +428,8 @@ class AareWrapper:
|
||||
headers=headers,
|
||||
data=json.dumps(payload),
|
||||
timeout=30,
|
||||
verify=self.__ssl_ca_cert,
|
||||
cert=(self.__cert_file, self.__key_file),
|
||||
verify=self._ssl_ca_cert,
|
||||
cert=(self._cert_file, self._key_file),
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
|
||||
+17
-22
@@ -1,5 +1,4 @@
|
||||
import grp
|
||||
import ipaddress
|
||||
import logging
|
||||
import os
|
||||
import pwd
|
||||
@@ -20,16 +19,12 @@ from fastapi import Depends, Request
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from pydantic import BaseModel
|
||||
|
||||
from aare.beamline_dispatch.protocols import AuthDispatch
|
||||
from aare.daq.config import BeamlineConfig
|
||||
|
||||
logger = logging.getLogger("aareDAQ")
|
||||
if os.environ.get("JWT_AAREDAQ_KEY") is None:
|
||||
raise Exception(
|
||||
"JWT_AAREDAQ_KEY environment variable not set, cannot guarantee safe authentication."
|
||||
)
|
||||
|
||||
SECRET_KEY = os.environ.get("JWT_AAREDAQ_KEY")
|
||||
|
||||
_SECRET_KEY: str | None = None
|
||||
ALGORITHM = "HS256"
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES = 24 * 60 * 7 # 1 week
|
||||
SESSION_EXPIRE_SECONDS = 60 * 10
|
||||
@@ -48,26 +43,26 @@ class TokenData(BaseModel):
|
||||
staff: bool = False
|
||||
|
||||
|
||||
def init_jwt_key(dispatch: AuthDispatch):
|
||||
global _SECRET_KEY
|
||||
_SECRET_KEY = dispatch.get_jwt_key()
|
||||
|
||||
|
||||
def jwt_key() -> str:
|
||||
if _SECRET_KEY is None:
|
||||
raise RuntimeError(
|
||||
"Run auth.init_jwt_key() to initialize it from the environment before trying to use the key."
|
||||
)
|
||||
return _SECRET_KEY
|
||||
|
||||
|
||||
def create_access_token(token: TokenData):
|
||||
to_encode = token.model_dump()
|
||||
to_encode.update({"exp": datetime.now(UTC) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)})
|
||||
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
||||
encoded_jwt = jwt.encode(to_encode, jwt_key(), algorithm=ALGORITHM)
|
||||
return encoded_jwt
|
||||
|
||||
|
||||
def _is_loopback(host: str | None) -> bool:
|
||||
if host is None:
|
||||
return False
|
||||
try:
|
||||
addr = ipaddress.ip_address(host)
|
||||
# Unwrap IPv4-mapped IPv6 (e.g. ::ffff:127.0.0.1) before checking
|
||||
if isinstance(addr, ipaddress.IPv6Address) and addr.ipv4_mapped is not None:
|
||||
return addr.ipv4_mapped.is_loopback
|
||||
return addr.is_loopback
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
def authenticate_from_proxy_header(request: Request) -> str:
|
||||
"""
|
||||
Extract the pre-authenticated username from the trusted Apache proxy header.
|
||||
@@ -104,7 +99,7 @@ def authenticate_user(cfg: BeamlineConfig, username: str) -> str:
|
||||
|
||||
def parse_token(token: str = Depends(oauth2_scheme)) -> TokenData:
|
||||
try:
|
||||
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
||||
payload = jwt.decode(token, jwt_key(), algorithms=[ALGORITHM])
|
||||
token = TokenData(**payload)
|
||||
return token
|
||||
except jwt.PyJWTError as e:
|
||||
|
||||
+169
-169
@@ -40,6 +40,7 @@ from aarecommon.models.models import (
|
||||
ZoomModel,
|
||||
zoom_manager,
|
||||
)
|
||||
|
||||
from aare.daq.config_model import LocalContactConfigModel
|
||||
|
||||
# TODO WHAT SHOULD THIS BE? This should be in the YAMl file it is beamline specific
|
||||
@@ -94,13 +95,13 @@ class BeamlineConfig:
|
||||
GUI_SESSION_EXPIRE_SECONDS = 60 * 10
|
||||
|
||||
def __init__(self, bl: MXBeamline):
|
||||
self.__mxb = bl
|
||||
self.__bl = bl.value.lower()
|
||||
self._mxb = bl
|
||||
self._bl = bl.value.lower()
|
||||
if bl is MXBeamline.SIMULATED:
|
||||
host = "localhost"
|
||||
else:
|
||||
host = cfg_get("daq.hardware.redis_url", f"{self.__bl}-redis.psi.ch")
|
||||
self.__client = redis.Redis(host=host, port=6379, db=0, decode_responses=True)
|
||||
host = cfg_get("daq.hardware.redis_url", f"{self._bl}-redis.psi.ch")
|
||||
self._client = redis.Redis(host=host, port=6379, db=0, decode_responses=True)
|
||||
self.simulated_detector = bl is MXBeamline.SIMULATED
|
||||
self._initialize_optional_yaml_defaults()
|
||||
|
||||
@@ -127,17 +128,17 @@ class BeamlineConfig:
|
||||
# GUI session management
|
||||
|
||||
def _gui_sessions_index_key(self) -> str:
|
||||
return f"{self.__bl}:gui_sessions"
|
||||
return f"{self._bl}:gui_sessions"
|
||||
|
||||
def _gui_session_key(self, session: int) -> str:
|
||||
return f"{self.__bl}:gui_session:{session}"
|
||||
return f"{self._bl}:gui_session:{session}"
|
||||
|
||||
def _write_gui_session(
|
||||
self, payload: OpenGuiSessionInfo, expiry_sec: int | None = None
|
||||
) -> None:
|
||||
expiry = int(expiry_sec or self.GUI_SESSION_EXPIRE_SECONDS)
|
||||
|
||||
pipe = self.__client.pipeline()
|
||||
pipe = self._client.pipeline()
|
||||
pipe.set(self._gui_session_key(payload.session), payload.model_dump_json())
|
||||
pipe.expire(self._gui_session_key(payload.session), expiry)
|
||||
pipe.sadd(self._gui_sessions_index_key(), payload.session)
|
||||
@@ -145,7 +146,7 @@ class BeamlineConfig:
|
||||
|
||||
def _current_gui_session_ttl(self, session: int) -> int | None:
|
||||
try:
|
||||
ttl = int(self.__client.ttl(self._gui_session_key(session)))
|
||||
ttl = int(self._client.ttl(self._gui_session_key(session)))
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
@@ -154,7 +155,7 @@ class BeamlineConfig:
|
||||
return None
|
||||
|
||||
def _read_gui_session(self, session: int) -> OpenGuiSessionInfo | None:
|
||||
raw = self.__client.get(self._gui_session_key(session))
|
||||
raw = self._client.get(self._gui_session_key(session))
|
||||
if raw is None:
|
||||
return None
|
||||
try:
|
||||
@@ -192,7 +193,7 @@ class BeamlineConfig:
|
||||
) -> OpenGuiSessionInfo | None:
|
||||
payload = self._read_gui_session(session)
|
||||
if payload is None:
|
||||
self.__client.srem(self._gui_sessions_index_key(), session)
|
||||
self._client.srem(self._gui_sessions_index_key(), session)
|
||||
return None
|
||||
|
||||
payload.last_interaction_ts = last_interaction_ts
|
||||
@@ -205,7 +206,7 @@ class BeamlineConfig:
|
||||
) -> OpenGuiSessionInfo | None:
|
||||
payload = self._read_gui_session(session)
|
||||
if payload is None:
|
||||
self.__client.srem(self._gui_sessions_index_key(), session)
|
||||
self._client.srem(self._gui_sessions_index_key(), session)
|
||||
return None
|
||||
|
||||
payload.close_requested = True
|
||||
@@ -220,7 +221,7 @@ class BeamlineConfig:
|
||||
def clear_gui_close_request(self, session: int) -> None:
|
||||
payload = self._read_gui_session(session)
|
||||
if payload is None:
|
||||
self.__client.srem(self._gui_sessions_index_key(), session)
|
||||
self._client.srem(self._gui_sessions_index_key(), session)
|
||||
return
|
||||
|
||||
payload.close_requested = False
|
||||
@@ -232,29 +233,29 @@ class BeamlineConfig:
|
||||
self._write_gui_session(payload, expiry_sec=ttl)
|
||||
|
||||
def remove_gui_session(self, session: int) -> None:
|
||||
pipe = self.__client.pipeline()
|
||||
pipe = self._client.pipeline()
|
||||
pipe.delete(self._gui_session_key(session))
|
||||
pipe.srem(self._gui_sessions_index_key(), session)
|
||||
pipe.execute()
|
||||
self.purge_expired_gui_sessions()
|
||||
|
||||
def purge_expired_gui_sessions(self) -> None:
|
||||
session_ids = self.__client.smembers(self._gui_sessions_index_key())
|
||||
session_ids = self._client.smembers(self._gui_sessions_index_key())
|
||||
if not session_ids:
|
||||
return
|
||||
|
||||
expired_ids: list[str] = []
|
||||
for session_id in session_ids:
|
||||
if not self.__client.exists(self._gui_session_key(int(session_id))):
|
||||
if not self._client.exists(self._gui_session_key(int(session_id))):
|
||||
expired_ids.append(session_id)
|
||||
|
||||
if expired_ids:
|
||||
self.__client.srem(self._gui_sessions_index_key(), *expired_ids)
|
||||
self._client.srem(self._gui_sessions_index_key(), *expired_ids)
|
||||
|
||||
def get_open_gui_sessions(self) -> list[OpenGuiSessionInfo]:
|
||||
self.purge_expired_gui_sessions()
|
||||
|
||||
session_ids = self.__client.smembers(self._gui_sessions_index_key())
|
||||
session_ids = self._client.smembers(self._gui_sessions_index_key())
|
||||
if not session_ids:
|
||||
return []
|
||||
|
||||
@@ -273,7 +274,7 @@ class BeamlineConfig:
|
||||
def get_gui_session(self, session: int) -> OpenGuiSessionInfo | None:
|
||||
payload = self._read_gui_session(session)
|
||||
if payload is None:
|
||||
self.__client.srem(self._gui_sessions_index_key(), session)
|
||||
self._client.srem(self._gui_sessions_index_key(), session)
|
||||
return None
|
||||
|
||||
holder = self.baton_holder
|
||||
@@ -284,7 +285,7 @@ class BeamlineConfig:
|
||||
|
||||
@property
|
||||
def allow_non_staff_request_from_staff(self) -> bool:
|
||||
raw = self.__client.get(f"{self.__bl}:allow_non_staff_request_from_staff")
|
||||
raw = self._client.get(f"{self._bl}:allow_non_staff_request_from_staff")
|
||||
if raw is None:
|
||||
return False
|
||||
return str(raw).strip().lower() in {"1", "true", "yes", "on"}
|
||||
@@ -292,12 +293,12 @@ class BeamlineConfig:
|
||||
@allow_non_staff_request_from_staff.setter
|
||||
def allow_non_staff_request_from_staff(self, enabled: bool) -> None:
|
||||
if enabled:
|
||||
self.__client.set(f"{self.__bl}:allow_non_staff_request_from_staff", "1")
|
||||
self._client.set(f"{self._bl}:allow_non_staff_request_from_staff", "1")
|
||||
else:
|
||||
self.__client.delete(f"{self.__bl}:allow_non_staff_request_from_staff")
|
||||
self._client.delete(f"{self._bl}:allow_non_staff_request_from_staff")
|
||||
|
||||
def generate_session(self) -> int:
|
||||
return int(self.__client.incr(f"{self.__bl}:session"))
|
||||
return int(self._client.incr(f"{self._bl}:session"))
|
||||
|
||||
@property
|
||||
def active_session(self) -> int | None:
|
||||
@@ -342,60 +343,60 @@ class BeamlineConfig:
|
||||
return SessionsStateEnum.OwnedByElse
|
||||
|
||||
def try_set_active_session(self, session: int, expiry_sec: int) -> None:
|
||||
with redis_lock.Lock(self.__client, f"{self.__bl}:active_session_lock", expire=10):
|
||||
with redis_lock.Lock(self._client, f"{self._bl}:active_session_lock", expire=10):
|
||||
active = self.active_session
|
||||
if active is None:
|
||||
self.__client.set(f"{self.__bl}:active_session", session)
|
||||
self._client.set(f"{self._bl}:active_session", session)
|
||||
elif active != session:
|
||||
raise Exception(
|
||||
"There is already active session with different id. Try again later."
|
||||
)
|
||||
self.__client.expire(f"{self.__bl}:active_session", expiry_sec)
|
||||
self._client.expire(f"{self._bl}:active_session", expiry_sec)
|
||||
|
||||
# TODO finish setting this up!
|
||||
def try_extend_active_session(self, session: int, expiry_sec: int) -> None:
|
||||
with redis_lock.Lock(self.__client, f"{self.__bl}:active_session_lock", expire=10):
|
||||
with redis_lock.Lock(self._client, f"{self._bl}:active_session_lock", expire=10):
|
||||
active = self.active_session
|
||||
if active is None:
|
||||
raise Exception("There is no active session with given id. Try again later.")
|
||||
# if active == session:
|
||||
# self.__client.expire(f"{self.__bl}:active_session", expiry_sec, gt=True)
|
||||
# self._client.expire(f"{self._bl}:active_session", expiry_sec, gt=True)
|
||||
|
||||
if active == session:
|
||||
key = f"{self.__bl}:active_session"
|
||||
current_ttl = self.__client.ttl(key)
|
||||
key = f"{self._bl}:active_session"
|
||||
current_ttl = self._client.ttl(key)
|
||||
|
||||
# Redis compatibility:
|
||||
# some Redis servers do not support EXPIRE with GT option.
|
||||
# Emulate "extend only if greater" manually while holding the lock.
|
||||
if current_ttl is None or current_ttl < 0 or current_ttl < expiry_sec:
|
||||
self.__client.expire(key, expiry_sec)
|
||||
self._client.expire(key, expiry_sec)
|
||||
else:
|
||||
raise Exception(
|
||||
"There is already active session with different id. Try again later."
|
||||
)
|
||||
|
||||
def end_active_session(self, session: int) -> None:
|
||||
with redis_lock.Lock(self.__client, f"{self.__bl}:active_session_lock", expire=10):
|
||||
with redis_lock.Lock(self._client, f"{self._bl}:active_session_lock", expire=10):
|
||||
active = self.active_session
|
||||
if active is None:
|
||||
return
|
||||
if active == session:
|
||||
self.__client.delete(f"{self.__bl}:active_session")
|
||||
self.__client.delete(f"{self.__bl}:baton_holder")
|
||||
self._client.delete(f"{self._bl}:active_session")
|
||||
self._client.delete(f"{self._bl}:baton_holder")
|
||||
|
||||
def force_set_active_session(self, session: int, expiry_sec: int) -> None:
|
||||
# Ensure that there is no active try-set for active session
|
||||
with redis_lock.Lock(self.__client, f"{self.__bl}:active_session_lock", expire=10):
|
||||
self.__client.set(f"{self.__bl}:active_session", session)
|
||||
self.__client.expire(f"{self.__bl}:active_session", expiry_sec)
|
||||
with redis_lock.Lock(self._client, f"{self._bl}:active_session_lock", expire=10):
|
||||
self._client.set(f"{self._bl}:active_session", session)
|
||||
self._client.expire(f"{self._bl}:active_session", expiry_sec)
|
||||
|
||||
# ========== BATON SYSTEM ==========
|
||||
|
||||
@property
|
||||
def baton_holder(self) -> BatonHolderInfo | None:
|
||||
"""Get information about the current baton holder."""
|
||||
tmp = self.__client.get(f"{self.__bl}:baton_holder")
|
||||
tmp = self._client.get(f"{self._bl}:baton_holder")
|
||||
if tmp is None:
|
||||
return None
|
||||
try:
|
||||
@@ -406,14 +407,14 @@ class BeamlineConfig:
|
||||
@baton_holder.setter
|
||||
def baton_holder(self, info: BatonHolderInfo | None) -> None:
|
||||
if info is None:
|
||||
self.__client.delete(f"{self.__bl}:baton_holder")
|
||||
self._client.delete(f"{self._bl}:baton_holder")
|
||||
else:
|
||||
self.__client.set(f"{self.__bl}:baton_holder", info.model_dump_json())
|
||||
self._client.set(f"{self._bl}:baton_holder", info.model_dump_json())
|
||||
|
||||
@property
|
||||
def pending_baton_request(self) -> BatonRequest | None:
|
||||
"""Get the current pending baton request, if any."""
|
||||
tmp = self.__client.get(f"{self.__bl}:baton_request")
|
||||
tmp = self._client.get(f"{self._bl}:baton_request")
|
||||
if tmp is None:
|
||||
return None
|
||||
try:
|
||||
@@ -426,19 +427,19 @@ class BeamlineConfig:
|
||||
) -> None:
|
||||
"""Set a pending baton request with auto-expiry for timeout."""
|
||||
if request is None:
|
||||
self.__client.delete(f"{self.__bl}:baton_request")
|
||||
self._client.delete(f"{self._bl}:baton_request")
|
||||
else:
|
||||
self.__client.set(f"{self.__bl}:baton_request", request.model_dump_json())
|
||||
self._client.set(f"{self._bl}:baton_request", request.model_dump_json())
|
||||
# Add a few seconds buffer so we can detect timeout vs expiry
|
||||
self.__client.expire(f"{self.__bl}:baton_request", timeout_sec + 5)
|
||||
self._client.expire(f"{self._bl}:baton_request", timeout_sec + 5)
|
||||
|
||||
def clear_pending_baton_request(self) -> None:
|
||||
self.__client.delete(f"{self.__bl}:baton_request")
|
||||
self._client.delete(f"{self._bl}:baton_request")
|
||||
|
||||
@property
|
||||
def queued_baton_transfer(self) -> BatonTransferQueue | None:
|
||||
"""Get queued transfer waiting for beamline to be available."""
|
||||
tmp = self.__client.get(f"{self.__bl}:baton_transfer_queue")
|
||||
tmp = self._client.get(f"{self._bl}:baton_transfer_queue")
|
||||
if tmp is None:
|
||||
return None
|
||||
try:
|
||||
@@ -449,9 +450,9 @@ class BeamlineConfig:
|
||||
@queued_baton_transfer.setter
|
||||
def queued_baton_transfer(self, transfer: BatonTransferQueue | None) -> None:
|
||||
if transfer is None:
|
||||
self.__client.delete(f"{self.__bl}:baton_transfer_queue")
|
||||
self._client.delete(f"{self._bl}:baton_transfer_queue")
|
||||
else:
|
||||
self.__client.set(f"{self.__bl}:baton_transfer_queue", transfer.model_dump_json())
|
||||
self._client.set(f"{self._bl}:baton_transfer_queue", transfer.model_dump_json())
|
||||
|
||||
def can_transfer_baton_now(self) -> bool:
|
||||
"""Check if baton can be transferred (beamline not mid-operation)."""
|
||||
@@ -475,9 +476,9 @@ class BeamlineConfig:
|
||||
Atomically transfer the baton to a new holder.
|
||||
Use existing active_session_lock for consistency.
|
||||
"""
|
||||
with redis_lock.Lock(self.__client, f"{self.__bl}:active_session_lock", expire=10):
|
||||
self.__client.set(f"{self.__bl}:active_session", to_session)
|
||||
self.__client.expire(f"{self.__bl}:active_session", expiry_sec)
|
||||
with redis_lock.Lock(self._client, f"{self._bl}:active_session_lock", expire=10):
|
||||
self._client.set(f"{self._bl}:active_session", to_session)
|
||||
self._client.expire(f"{self._bl}:active_session", expiry_sec)
|
||||
self.baton_holder = BatonHolderInfo(
|
||||
username=to_username, session=to_session, is_staff=to_is_staff, pgroup=to_pgroup
|
||||
)
|
||||
@@ -508,19 +509,19 @@ class BeamlineConfig:
|
||||
|
||||
@property
|
||||
def pgroup(self) -> str | None:
|
||||
tmp = self.__client.get(f"{self.__bl}:pgroup")
|
||||
tmp = self._client.get(f"{self._bl}:pgroup")
|
||||
return tmp
|
||||
|
||||
@pgroup.setter
|
||||
def pgroup(self, pgroup: str | None) -> None:
|
||||
if pgroup is None:
|
||||
self.__client.delete(f"{self.__bl}:pgroup")
|
||||
self._client.delete(f"{self._bl}:pgroup")
|
||||
else:
|
||||
self.__client.set(f"{self.__bl}:pgroup", pgroup)
|
||||
self._client.set(f"{self._bl}:pgroup", pgroup)
|
||||
|
||||
@property
|
||||
def commissioning_mode(self) -> bool:
|
||||
tmp = self.__client.get(f"{self.__bl}:commissioning_mode")
|
||||
tmp = self._client.get(f"{self._bl}:commissioning_mode")
|
||||
if tmp is None:
|
||||
return False
|
||||
return True
|
||||
@@ -528,19 +529,19 @@ class BeamlineConfig:
|
||||
@commissioning_mode.setter
|
||||
def commissioning_mode(self, commisioning_mode: bool) -> None:
|
||||
if commisioning_mode:
|
||||
self.__client.set(f"{self.__bl}:commissioning_mode", "1")
|
||||
self._client.set(f"{self._bl}:commissioning_mode", "1")
|
||||
else:
|
||||
self.__client.delete(f"{self.__bl}:commissioning_mode")
|
||||
self._client.delete(f"{self._bl}:commissioning_mode")
|
||||
|
||||
# Beamline state management
|
||||
# Atomic check if beamline is busy and if not set state to busy
|
||||
def try_set_busy(self, timeout: int | None = None):
|
||||
with redis_lock.Lock(self.__client, f"{self.__bl}:move_state_lock", expire=10):
|
||||
with redis_lock.Lock(self._client, f"{self._bl}:move_state_lock", expire=10):
|
||||
if self.state_busy:
|
||||
raise BeamlineBusyException("Beamline is busy")
|
||||
self.state_busy = True
|
||||
if timeout is not None:
|
||||
self.__client.expire(f"{self.__bl}:busy", timeout)
|
||||
self._client.expire(f"{self._bl}:busy", timeout)
|
||||
|
||||
def set_busy(self, target: BeamlineStateEnum, timeout: int | None = None):
|
||||
self.try_set_busy(timeout=timeout)
|
||||
@@ -562,7 +563,7 @@ class BeamlineConfig:
|
||||
|
||||
@property
|
||||
def state(self) -> BeamlineStateEnum:
|
||||
raw_value = self.__client.get(f"{self.__bl}:state")
|
||||
raw_value = self._client.get(f"{self._bl}:state")
|
||||
|
||||
if raw_value is None:
|
||||
return BeamlineStateEnum.Maintenance
|
||||
@@ -576,24 +577,24 @@ class BeamlineConfig:
|
||||
|
||||
@state.setter
|
||||
def state(self, state: BeamlineStateEnum):
|
||||
self.__client.set(f"{self.__bl}:state", state.value)
|
||||
self._client.set(f"{self._bl}:state", state.value)
|
||||
|
||||
@property
|
||||
def state_busy(self) -> bool:
|
||||
return self.__client.get(f"{self.__bl}:busy") is not None
|
||||
return self._client.get(f"{self._bl}:busy") is not None
|
||||
|
||||
@state_busy.setter
|
||||
def state_busy(self, i: bool):
|
||||
if i:
|
||||
self.__client.set(f"{self.__bl}:busy", "1")
|
||||
self._client.set(f"{self._bl}:busy", "1")
|
||||
else:
|
||||
self.__client.delete(f"{self.__bl}:busy")
|
||||
self._client.delete(f"{self._bl}:busy")
|
||||
|
||||
# Other beamline settings
|
||||
|
||||
@property
|
||||
def tell_mount_count(self) -> int:
|
||||
return int(self.__client.incr(f"{self.__bl}:tell_mount_count"))
|
||||
return int(self._client.incr(f"{self._bl}:tell_mount_count"))
|
||||
|
||||
def pixel_to_mm(self, zoom: float) -> float:
|
||||
cfg = self.settings
|
||||
@@ -635,8 +636,8 @@ class BeamlineConfig:
|
||||
|
||||
@property
|
||||
def beam_center(self) -> Tuple[float, float]:
|
||||
tmp_x = self.__client.get(f"{self.__bl}:beam_center_x")
|
||||
tmp_y = self.__client.get(f"{self.__bl}:beam_center_y")
|
||||
tmp_x = self._client.get(f"{self._bl}:beam_center_x")
|
||||
tmp_y = self._client.get(f"{self._bl}:beam_center_y")
|
||||
if tmp_x:
|
||||
val_x = float(tmp_x)
|
||||
else:
|
||||
@@ -649,13 +650,13 @@ class BeamlineConfig:
|
||||
|
||||
@beam_center.setter
|
||||
def beam_center(self, data: Tuple[float, float]):
|
||||
self.__client.set(f"{self.__bl}:beam_center_x", data[0])
|
||||
self.__client.set(f"{self.__bl}:beam_center_y", data[1])
|
||||
self._client.set(f"{self._bl}:beam_center_x", data[0])
|
||||
self._client.set(f"{self._bl}:beam_center_y", data[1])
|
||||
|
||||
@property
|
||||
def beam_size_mm(self) -> Coordinate:
|
||||
tmp_x = self.__client.get(f"{self.__bl}:beam_size_x")
|
||||
tmp_y = self.__client.get(f"{self.__bl}:beam_size_y")
|
||||
tmp_x = self._client.get(f"{self._bl}:beam_size_x")
|
||||
tmp_y = self._client.get(f"{self._bl}:beam_size_y")
|
||||
if tmp_x:
|
||||
val_x = float(tmp_x)
|
||||
else:
|
||||
@@ -668,11 +669,11 @@ class BeamlineConfig:
|
||||
|
||||
@beam_size_mm.setter
|
||||
def beam_size_mm(self, data: Coordinate):
|
||||
self.__client.set(f"{self.__bl}:beam_size_x", data.x)
|
||||
self.__client.set(f"{self.__bl}:beam_size_y", data.y)
|
||||
self._client.set(f"{self._bl}:beam_size_x", data.x)
|
||||
self._client.set(f"{self._bl}:beam_size_y", data.y)
|
||||
|
||||
def __get_settings(self) -> BeamlineSettingsModel:
|
||||
tmp = self.__client.get(f"{self.__bl}:settings")
|
||||
def _get_settings(self) -> BeamlineSettingsModel:
|
||||
tmp = self._client.get(f"{self._bl}:settings")
|
||||
if tmp is None:
|
||||
return BeamlineSettingsModel()
|
||||
|
||||
@@ -682,19 +683,19 @@ class BeamlineConfig:
|
||||
|
||||
@property
|
||||
def settings(self) -> BeamlineSettingsModel:
|
||||
with redis_lock.Lock(self.__client, f"{self.__bl}:settings_lock", expire=10):
|
||||
return self.__get_settings()
|
||||
with redis_lock.Lock(self._client, f"{self._bl}:settings_lock", expire=10):
|
||||
return self._get_settings()
|
||||
|
||||
@settings.setter
|
||||
def settings(self, data: BeamlineSettingsModel):
|
||||
with redis_lock.Lock(self.__client, f"{self.__bl}:settings_lock", expire=10):
|
||||
current = self.__get_settings()
|
||||
with redis_lock.Lock(self._client, f"{self._bl}:settings_lock", expire=10):
|
||||
current = self._get_settings()
|
||||
updated_data = current.model_copy(update=data.model_dump(exclude_unset=True))
|
||||
self.__client.set(f"{self.__bl}:settings", updated_data.model_dump_json())
|
||||
self._client.set(f"{self._bl}:settings", updated_data.model_dump_json())
|
||||
|
||||
@property
|
||||
def cryojet_settings(self) -> CryojetSettingsModel:
|
||||
tmp = self.__client.get(f"{self.__bl}:cryojet_settings")
|
||||
tmp = self._client.get(f"{self._bl}:cryojet_settings")
|
||||
if tmp is None:
|
||||
return CryojetSettingsModel()
|
||||
|
||||
@@ -703,17 +704,17 @@ class BeamlineConfig:
|
||||
|
||||
@cryojet_settings.setter
|
||||
def cryojet_settings(self, data: CryojetSettingsModel):
|
||||
self.__client.set(f"{self.__bl}:cryojet_settings", data.model_dump_json())
|
||||
self._client.set(f"{self._bl}:cryojet_settings", data.model_dump_json())
|
||||
|
||||
def get_alc_bkg(self, zoom: float, exp: float, gain: float) -> np.ndarray | None:
|
||||
return base64_to_numpy(self.__client.get(f"{self.__bl}:bkg{zoom:.1f}_{exp:.2f}_{gain:.1f}"))
|
||||
return base64_to_numpy(self._client.get(f"{self._bl}:bkg{zoom:.1f}_{exp:.2f}_{gain:.1f}"))
|
||||
|
||||
def put_alc_bkg(self, zoom: float, exp: float, gain: float, data: np.ndarray):
|
||||
self.__client.set(f"{self.__bl}:bkg{zoom:.1f}_{exp:.2f}_{gain:.1f}", numpy_to_base64(data))
|
||||
self._client.set(f"{self._bl}:bkg{zoom:.1f}_{exp:.2f}_{gain:.1f}", numpy_to_base64(data))
|
||||
|
||||
@property
|
||||
def spreadsheet(self) -> SampleShortInfoList:
|
||||
tmp = self.__client.get(f"{self.__bl}:sample_spreadsheet")
|
||||
tmp = self._client.get(f"{self._bl}:sample_spreadsheet")
|
||||
if tmp is None:
|
||||
return SampleShortInfoList(s=[])
|
||||
|
||||
@@ -727,17 +728,17 @@ class BeamlineConfig:
|
||||
|
||||
@spreadsheet.setter
|
||||
def spreadsheet(self, data: SampleShortInfoList):
|
||||
self.__client.set(f"{self.__bl}:sample_spreadsheet", data.model_dump_json())
|
||||
self._client.set(f"{self._bl}:sample_spreadsheet", data.model_dump_json())
|
||||
|
||||
def listen_changes_spreadsheet(self) -> redis.client.PubSub:
|
||||
self.__client.config_set("notify-keyspace-events", "KEA")
|
||||
pubsub = self.__client.pubsub()
|
||||
pubsub.psubscribe(f"__keyspace@0__:{self.__bl}:sample_spreadsheet")
|
||||
self._client.config_set("notify-keyspace-events", "KEA")
|
||||
pubsub = self._client.pubsub()
|
||||
pubsub.psubscribe(f"__keyspace@0__:{self._bl}:sample_spreadsheet")
|
||||
return pubsub
|
||||
|
||||
@property
|
||||
def reference_tools(self) -> SampleShortInfoList:
|
||||
tmp = self.__client.get(f"{self.__bl}:reference-tools")
|
||||
tmp = self._client.get(f"{self._bl}:reference-tools")
|
||||
if tmp is None:
|
||||
return SampleShortInfoList(s=[])
|
||||
|
||||
@@ -746,17 +747,17 @@ class BeamlineConfig:
|
||||
|
||||
@reference_tools.setter
|
||||
def reference_tools(self, data: SampleShortInfoList):
|
||||
self.__client.set(f"{self.__bl}:reference-tools", data.model_dump_json())
|
||||
self._client.set(f"{self._bl}:reference-tools", data.model_dump_json())
|
||||
|
||||
def listen_changes_reference_tools(self) -> redis.client.PubSub:
|
||||
self.__client.config_set("notify-keyspace-events", "KEA")
|
||||
pubsub = self.__client.pubsub()
|
||||
pubsub.psubscribe(f"__keyspace@0__:{self.__bl}:reference-tools")
|
||||
self._client.config_set("notify-keyspace-events", "KEA")
|
||||
pubsub = self._client.pubsub()
|
||||
pubsub.psubscribe(f"__keyspace@0__:{self._bl}:reference-tools")
|
||||
return pubsub
|
||||
|
||||
@property
|
||||
def current_sample(self) -> SampleShortInfo | None:
|
||||
tmp = self.__client.get(f"{self.__bl}:current_sample")
|
||||
tmp = self._client.get(f"{self._bl}:current_sample")
|
||||
if tmp is None:
|
||||
return None
|
||||
|
||||
@@ -766,13 +767,13 @@ class BeamlineConfig:
|
||||
@current_sample.setter
|
||||
def current_sample(self, sample: SampleShortInfo | None):
|
||||
if sample is None:
|
||||
self.__client.delete(f"{self.__bl}:current_sample")
|
||||
self._client.delete(f"{self._bl}:current_sample")
|
||||
else:
|
||||
self.__client.set(f"{self.__bl}:current_sample", sample.model_dump_json())
|
||||
self._client.set(f"{self._bl}:current_sample", sample.model_dump_json())
|
||||
|
||||
@property
|
||||
def beam_mark_coeff(self) -> BeamMarkCoeffModel:
|
||||
tmp = self.__client.get(f"{self.__bl}:beam_center_camera")
|
||||
tmp = self._client.get(f"{self._bl}:beam_center_camera")
|
||||
if tmp is None:
|
||||
return BeamMarkCoeffModel()
|
||||
|
||||
@@ -781,12 +782,12 @@ class BeamlineConfig:
|
||||
|
||||
@beam_mark_coeff.setter
|
||||
def beam_mark_coeff(self, data: BeamMarkCoeffModel):
|
||||
self.__client.set(f"{self.__bl}:beam_center_camera", data.model_dump_json())
|
||||
self._client.set(f"{self._bl}:beam_center_camera", data.model_dump_json())
|
||||
|
||||
# TODO tidy up zoom functions
|
||||
@property
|
||||
def zoom_mode(self) -> ZoomModeEnum:
|
||||
raw_value = self.__client.get(f"{self.__bl}:zoom_mode")
|
||||
raw_value = self._client.get(f"{self._bl}:zoom_mode")
|
||||
if raw_value is None:
|
||||
print("no zoom mode given, defaulting to user mode")
|
||||
return ZoomModeEnum.User
|
||||
@@ -800,7 +801,7 @@ class BeamlineConfig:
|
||||
|
||||
@zoom_mode.setter
|
||||
def zoom_mode(self, mode: ZoomModeEnum):
|
||||
self.__client.set(f"{self.__bl}:zoom_mode", mode.value)
|
||||
self._client.set(f"{self._bl}:zoom_mode", mode.value)
|
||||
|
||||
@staticmethod
|
||||
def zoom_setting_string(mode: ZoomModeEnum = ZoomModeEnum.User) -> str:
|
||||
@@ -820,9 +821,9 @@ class BeamlineConfig:
|
||||
mode = self.zoom_mode
|
||||
if not mode or not isinstance(mode, ZoomModeEnum):
|
||||
raise Exception("incorrect zoom settings mode used")
|
||||
tmp = self.__client.get(f"{self.__bl}:{self.zoom_setting_string(mode)}")
|
||||
tmp = self._client.get(f"{self._bl}:{self.zoom_setting_string(mode)}")
|
||||
if tmp is None:
|
||||
return zoom_manager(mode, self.__mxb)
|
||||
return zoom_manager(mode, self._mxb)
|
||||
data_dict = json.loads(tmp)
|
||||
return ZoomModel(**data_dict)
|
||||
|
||||
@@ -831,7 +832,7 @@ class BeamlineConfig:
|
||||
mode = self.zoom_mode
|
||||
if not mode or not isinstance(mode, ZoomModeEnum):
|
||||
raise Exception("incorrect zoom settings mode used")
|
||||
self.__client.set(f"{self.__bl}:{self.zoom_setting_string(mode)}", data.model_dump_json())
|
||||
self._client.set(f"{self._bl}:{self.zoom_setting_string(mode)}", data.model_dump_json())
|
||||
|
||||
def save_zoom_camera_setting(
|
||||
self, zoom_value: float, settings: SampleCameraSettings, mode: ZoomModeEnum | None = None
|
||||
@@ -840,15 +841,15 @@ class BeamlineConfig:
|
||||
mode's table (persisted to Redis under that mode's key). Defaults to the
|
||||
active zoom mode; pass ``mode`` to target a specific one explicitly."""
|
||||
mode = mode or self.zoom_mode
|
||||
key = f"{self.__bl}:{self.zoom_setting_string(mode)}"
|
||||
tmp = self.__client.get(key)
|
||||
model = ZoomModel(**json.loads(tmp)) if tmp is not None else zoom_manager(mode, self.__mxb)
|
||||
key = f"{self._bl}:{self.zoom_setting_string(mode)}"
|
||||
tmp = self._client.get(key)
|
||||
model = ZoomModel(**json.loads(tmp)) if tmp is not None else zoom_manager(mode, self._mxb)
|
||||
model.z[zoom_value] = settings
|
||||
self.__client.set(key, model.model_dump_json())
|
||||
self._client.set(key, model.model_dump_json())
|
||||
|
||||
@property
|
||||
def abr_meas_pos(self) -> AerotechCoordinate:
|
||||
tmp = self.__client.get(f"{self.__bl}:abr_meas_pos")
|
||||
tmp = self._client.get(f"{self._bl}:abr_meas_pos")
|
||||
if tmp is None:
|
||||
return ABR_POS_MOUNT
|
||||
|
||||
@@ -857,33 +858,33 @@ class BeamlineConfig:
|
||||
|
||||
@abr_meas_pos.setter
|
||||
def abr_meas_pos(self, data: AerotechCoordinate):
|
||||
self.__client.set(f"{self.__bl}:abr_meas_pos", data.model_dump_json())
|
||||
self._client.set(f"{self._bl}:abr_meas_pos", data.model_dump_json())
|
||||
|
||||
@property
|
||||
def dtz(self) -> float | None:
|
||||
tmp = self.__client.get(f"{self.__bl}:dtz")
|
||||
tmp = self._client.get(f"{self._bl}:dtz")
|
||||
if tmp is None:
|
||||
return None
|
||||
return float(tmp)
|
||||
|
||||
@dtz.setter
|
||||
def dtz(self, dtz: float):
|
||||
self.__client.set(f"{self.__bl}:dtz", dtz)
|
||||
self._client.set(f"{self._bl}:dtz", dtz)
|
||||
|
||||
@property
|
||||
def dtz_safe_position(self) -> float | None:
|
||||
tmp = self.__client.get(f"{self.__bl}:dtz_safe_position")
|
||||
tmp = self._client.get(f"{self._bl}:dtz_safe_position")
|
||||
if tmp is None:
|
||||
return None
|
||||
return float(tmp)
|
||||
|
||||
@dtz_safe_position.setter
|
||||
def dtz_safe_position(self, dtz: float):
|
||||
self.__client.set(f"{self.__bl}:dtz_safe_position", dtz)
|
||||
self._client.set(f"{self._bl}:dtz_safe_position", dtz)
|
||||
|
||||
@property
|
||||
def xrf(self) -> FluorescenceSpectrumOutputModel | None:
|
||||
tmp = self.__client.get(f"{self.__bl}:xrf")
|
||||
tmp = self._client.get(f"{self._bl}:xrf")
|
||||
if tmp is None:
|
||||
return None
|
||||
data_dict = json.loads(tmp)
|
||||
@@ -892,18 +893,18 @@ class BeamlineConfig:
|
||||
@xrf.setter
|
||||
def xrf(self, data: FluorescenceSpectrumOutputModel | None):
|
||||
if data is None:
|
||||
self.__client.delete(f"{self.__bl}:xrf")
|
||||
self._client.delete(f"{self._bl}:xrf")
|
||||
else:
|
||||
self.__client.set(f"{self.__bl}:xrf", data.model_dump_json())
|
||||
self._client.set(f"{self._bl}:xrf", data.model_dump_json())
|
||||
|
||||
def clear_mark_beam(self):
|
||||
self.__client.delete(f"{self.__bl}:beam_mark")
|
||||
self._client.delete(f"{self._bl}:beam_mark")
|
||||
|
||||
def mark_beam(self, x_pxl: float, y_pxl: float, zoom: float):
|
||||
self.__client.hset(
|
||||
f"{self.__bl}:beam_mark", mapping={f"{zoom}": json.dumps({"x": x_pxl, "y": y_pxl})}
|
||||
self._client.hset(
|
||||
f"{self._bl}:beam_mark", mapping={f"{zoom}": json.dumps({"x": x_pxl, "y": y_pxl})}
|
||||
)
|
||||
vals = self.__client.hgetall(f"{self.__bl}:beam_mark")
|
||||
vals = self._client.hgetall(f"{self._bl}:beam_mark")
|
||||
|
||||
if len(vals) >= 3:
|
||||
zooms = []
|
||||
@@ -928,7 +929,7 @@ class BeamlineConfig:
|
||||
|
||||
@property
|
||||
def crystal_size(self) -> CrystalSize:
|
||||
tmp = self.__client.get(f"{self.__bl}:crystal_size")
|
||||
tmp = self._client.get(f"{self._bl}:crystal_size")
|
||||
if tmp is None:
|
||||
return CrystalSize(x=0, y=0, z=0)
|
||||
data_dict = json.loads(tmp)
|
||||
@@ -936,11 +937,11 @@ class BeamlineConfig:
|
||||
|
||||
@crystal_size.setter
|
||||
def crystal_size(self, xtal_size: CrystalSize):
|
||||
self.__client.set(f"{self.__bl}:crystal_size", xtal_size.model_dump_json())
|
||||
self._client.set(f"{self._bl}:crystal_size", xtal_size.model_dump_json())
|
||||
|
||||
@property
|
||||
def last_best_res(self) -> float | None:
|
||||
tmp = self.__client.get(f"{self.__bl}:last_best_res")
|
||||
tmp = self._client.get(f"{self._bl}:last_best_res")
|
||||
if tmp is None:
|
||||
return None
|
||||
return float(tmp)
|
||||
@@ -948,13 +949,13 @@ class BeamlineConfig:
|
||||
@last_best_res.setter
|
||||
def last_best_res(self, best_res: float | None):
|
||||
if best_res is None:
|
||||
self.__client.delete(f"{self.__bl}:last_best_res")
|
||||
self._client.delete(f"{self._bl}:last_best_res")
|
||||
else:
|
||||
self.__client.set(f"{self.__bl}:last_best_res", best_res)
|
||||
self._client.set(f"{self._bl}:last_best_res", best_res)
|
||||
|
||||
@property
|
||||
def last_best_b_factor(self) -> float | None:
|
||||
tmp = self.__client.get(f"{self.__bl}:last_best_b_factor")
|
||||
tmp = self._client.get(f"{self._bl}:last_best_b_factor")
|
||||
if tmp is None:
|
||||
return None
|
||||
return float(tmp)
|
||||
@@ -962,22 +963,22 @@ class BeamlineConfig:
|
||||
@last_best_b_factor.setter
|
||||
def last_best_b_factor(self, last_best_b_factor: float | None):
|
||||
if last_best_b_factor is None:
|
||||
self.__client.delete(f"{self.__bl}:last_best_b_factor")
|
||||
self._client.delete(f"{self._bl}:last_best_b_factor")
|
||||
else:
|
||||
self.__client.set(f"{self.__bl}:last_best_b_factor", last_best_b_factor)
|
||||
self._client.set(f"{self._bl}:last_best_b_factor", last_best_b_factor)
|
||||
|
||||
def _mount_failure_streak_key(self) -> str:
|
||||
return f"{self.__bl}:mount_fail_count"
|
||||
return f"{self._bl}:mount_fail_count"
|
||||
|
||||
def get_mount_failure_streak(self) -> int:
|
||||
value = self.__client.get(self._mount_failure_streak_key())
|
||||
value = self._client.get(self._mount_failure_streak_key())
|
||||
return int(value) if value else 0
|
||||
|
||||
def increment_mount_failure_streak(self) -> int:
|
||||
return int(self.__client.incr(self._mount_failure_streak_key()))
|
||||
return int(self._client.incr(self._mount_failure_streak_key()))
|
||||
|
||||
def reset_mount_failure_streak(self) -> None:
|
||||
self.__client.delete(self._mount_failure_streak_key())
|
||||
self._client.delete(self._mount_failure_streak_key())
|
||||
|
||||
def get_mount_fail_count(self) -> int:
|
||||
return self.get_mount_failure_streak()
|
||||
@@ -990,7 +991,7 @@ class BeamlineConfig:
|
||||
|
||||
@property
|
||||
def simple_input_parameters(self) -> SimpleStrategyInputModel | None:
|
||||
tmp = self.__client.get(f"{self.__bl}:simple_input_params")
|
||||
tmp = self._client.get(f"{self._bl}:simple_input_params")
|
||||
if tmp is None:
|
||||
return None
|
||||
data_dict = json.loads(tmp)
|
||||
@@ -999,15 +1000,15 @@ class BeamlineConfig:
|
||||
@simple_input_parameters.setter
|
||||
def simple_input_parameters(self, input_params: SimpleStrategyInputModel | None):
|
||||
if input_params is None:
|
||||
self.__client.delete(f"{self.__bl}:simple_input_params")
|
||||
self._client.delete(f"{self._bl}:simple_input_params")
|
||||
else:
|
||||
self.__client.set(f"{self.__bl}:simple_input_params", input_params.model_dump_json())
|
||||
self._client.set(f"{self._bl}:simple_input_params", input_params.model_dump_json())
|
||||
|
||||
@property
|
||||
def auto_params(self) -> SimpleScanParameters | None:
|
||||
tmp = self.__client.get(f"{self.__bl}:auto_params")
|
||||
tmp = self._client.get(f"{self._bl}:auto_params")
|
||||
if tmp is None:
|
||||
logger.debug(f"auto_params missing in redis key {self.__bl}:auto_params")
|
||||
logger.debug(f"auto_params missing in redis key {self._bl}:auto_params")
|
||||
return None
|
||||
try:
|
||||
data_dict = json.loads(tmp)
|
||||
@@ -1019,23 +1020,23 @@ class BeamlineConfig:
|
||||
@auto_params.setter
|
||||
def auto_params(self, params: SimpleScanParameters | None):
|
||||
if params is None:
|
||||
self.__client.delete(f"{self.__bl}:auto_params")
|
||||
self._client.delete(f"{self._bl}:auto_params")
|
||||
else:
|
||||
self.__client.set(f"{self.__bl}:auto_params", params.model_dump_json())
|
||||
self._client.set(f"{self._bl}:auto_params", params.model_dump_json())
|
||||
|
||||
def _automation_progress_key(self) -> str:
|
||||
return f"{self.__bl}:automation_progress"
|
||||
return f"{self._bl}:automation_progress"
|
||||
|
||||
def _automation_progress_seq_key(self) -> str:
|
||||
return f"{self.__bl}:automation_progress_seq"
|
||||
return f"{self._bl}:automation_progress_seq"
|
||||
|
||||
def reset_automation_progress(self) -> None:
|
||||
self.__client.set(self._automation_progress_seq_key(), 0)
|
||||
self.__client.delete(self._automation_progress_key())
|
||||
self._client.set(self._automation_progress_seq_key(), 0)
|
||||
self._client.delete(self._automation_progress_key())
|
||||
|
||||
def get_automation_progress_state(self) -> dict:
|
||||
seq_raw = self.__client.get(self._automation_progress_seq_key())
|
||||
payload_raw = self.__client.get(self._automation_progress_key())
|
||||
seq_raw = self._client.get(self._automation_progress_seq_key())
|
||||
payload_raw = self._client.get(self._automation_progress_key())
|
||||
|
||||
seq = int(seq_raw) if seq_raw is not None else 0
|
||||
progress = json.loads(payload_raw) if payload_raw else None
|
||||
@@ -1057,8 +1058,8 @@ class BeamlineConfig:
|
||||
else:
|
||||
raise TypeError(f"Unsupported automation progress type: {type(progress).__name__}")
|
||||
|
||||
next_seq = int(self.__client.incr(self._automation_progress_seq_key()))
|
||||
self.__client.set(
|
||||
next_seq = int(self._client.incr(self._automation_progress_seq_key()))
|
||||
self._client.set(
|
||||
self._automation_progress_key(),
|
||||
json.dumps(payload, separators=(",", ":"), default=_json_default),
|
||||
)
|
||||
@@ -1067,7 +1068,7 @@ class BeamlineConfig:
|
||||
|
||||
@property
|
||||
def failed_mount_count(self) -> int:
|
||||
tmp = self.__client.get(f"{self.__bl}:failed_mount_count")
|
||||
tmp = self._client.get(f"{self._bl}:failed_mount_count")
|
||||
if tmp is None:
|
||||
return 0
|
||||
try:
|
||||
@@ -1075,25 +1076,25 @@ class BeamlineConfig:
|
||||
except (TypeError, ValueError):
|
||||
logger.warning(
|
||||
"Failed Mount Count is not an integer, resetting to 0.",
|
||||
extra={"beamline": self.__bl, "tmp": tmp},
|
||||
extra={"beamline": self._bl, "tmp": tmp},
|
||||
)
|
||||
return 0
|
||||
|
||||
@failed_mount_count.setter
|
||||
def failed_mount_count(self, count: int):
|
||||
if count == 0:
|
||||
self.__client.delete(f"{self.__bl}:failed_mount_count")
|
||||
self._client.delete(f"{self._bl}:failed_mount_count")
|
||||
else:
|
||||
self.__client.set(f"{self.__bl}:failed_mount_count", count)
|
||||
self._client.set(f"{self._bl}:failed_mount_count", count)
|
||||
|
||||
def increment_failed_mount_count(self) -> int:
|
||||
return int(self.__client.incr(f"{self.__bl}:failed_mount_count"))
|
||||
return int(self._client.incr(f"{self._bl}:failed_mount_count"))
|
||||
|
||||
def _runtime_sim_key(self, name: str) -> str:
|
||||
return f"{self.__bl}:runtime:simulate:{name}"
|
||||
return f"{self._bl}:runtime:simulate:{name}"
|
||||
|
||||
def get_runtime_simulated(self, name: str, default: bool = False) -> bool:
|
||||
raw = self.__client.get(self._runtime_sim_key(name))
|
||||
raw = self._client.get(self._runtime_sim_key(name))
|
||||
if raw is None:
|
||||
return default
|
||||
return str(raw).strip().lower() in {"1", "true", "yes", "on"}
|
||||
@@ -1101,9 +1102,9 @@ class BeamlineConfig:
|
||||
def set_runtime_simulated(self, name: str, enabled: bool) -> None:
|
||||
key = self._runtime_sim_key(name)
|
||||
if enabled:
|
||||
self.__client.set(key, "1")
|
||||
self._client.set(key, "1")
|
||||
else:
|
||||
self.__client.delete(key)
|
||||
self._client.delete(key)
|
||||
|
||||
@property
|
||||
def simulate_bec(self) -> bool:
|
||||
@@ -1169,11 +1170,11 @@ class BeamlineConfig:
|
||||
return None
|
||||
|
||||
def _detector_metadata_key(self) -> str:
|
||||
return f"{self.__bl}:detector_metadata"
|
||||
return f"{self._bl}:detector_metadata"
|
||||
|
||||
def get_detector_metadata(self) -> dict:
|
||||
try:
|
||||
raw = self.__client.get(self._detector_metadata_key())
|
||||
raw = self._client.get(self._detector_metadata_key())
|
||||
if raw in (None, "", b""):
|
||||
return {}
|
||||
|
||||
@@ -1194,7 +1195,7 @@ class BeamlineConfig:
|
||||
safe_payload.get("pixel_size_mm")
|
||||
)
|
||||
safe_payload["updated_at"] = datetime.now().isoformat(timespec="seconds")
|
||||
self.__client.set(self._detector_metadata_key(), json.dumps(safe_payload))
|
||||
self._client.set(self._detector_metadata_key(), json.dumps(safe_payload))
|
||||
return safe_payload
|
||||
|
||||
@property
|
||||
@@ -1216,19 +1217,18 @@ class BeamlineConfig:
|
||||
detector_frontend = None
|
||||
smargon_frontend = cfg_get(
|
||||
"daq.hardware.smargon_frontend_url",
|
||||
f"http://{self.__mxb.name.lower()}-smargopolo.psi.ch:8080/",
|
||||
f"http://{self._mxb.name.lower()}-smargopolo.psi.ch:8080/",
|
||||
)
|
||||
aerotech_frontend = cfg_get(
|
||||
"daq.hardware.aerotech_url",
|
||||
f"http://mx-{self.__mxb.name.lower()}-queue-01.psi.ch:5234/",
|
||||
"daq.hardware.aerotech_url", f"http://mx-{self._mxb.name.lower()}-queue-01.psi.ch:5234/"
|
||||
)
|
||||
tell_hint = "Please check TELL status via Remmina / VNC."
|
||||
|
||||
if self.__mxb is MXBeamline.X06DA:
|
||||
if self._mxb is MXBeamline.X06DA:
|
||||
detector_frontend = "http://sls-gpu-001:8080/frontend"
|
||||
elif self.__mxb is MXBeamline.X10SA:
|
||||
elif self._mxb is MXBeamline.X10SA:
|
||||
detector_frontend = "http://sls-gpu-002:8080/frontend"
|
||||
elif self.__mxb is MXBeamline.SIMULATED:
|
||||
elif self._mxb is MXBeamline.SIMULATED:
|
||||
detector_frontend = None
|
||||
smargon_frontend = None
|
||||
aerotech_frontend = None
|
||||
@@ -1244,8 +1244,8 @@ class BeamlineConfig:
|
||||
default = LocalContactConfigModel()
|
||||
|
||||
try:
|
||||
redis_key = f"{self.__bl}:local_contact_config"
|
||||
raw_value = self.__client.get(redis_key)
|
||||
redis_key = f"{self._bl}:local_contact_config"
|
||||
raw_value = self._client.get(redis_key)
|
||||
|
||||
if raw_value in (None, "", b""):
|
||||
return default
|
||||
@@ -1263,8 +1263,8 @@ class BeamlineConfig:
|
||||
) -> LocalContactConfigModel:
|
||||
validated = LocalContactConfigModel.model_validate(config)
|
||||
try:
|
||||
redis_key = f"{self.__bl}:local_contact_config"
|
||||
self.__client.set(redis_key, validated.model_dump_json())
|
||||
redis_key = f"{self._bl}:local_contact_config"
|
||||
self._client.set(redis_key, validated.model_dump_json())
|
||||
logger.info(f"Saved Local Contact config to Redis: {redis_key}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to write Local Contact config to Redis: {e}")
|
||||
|
||||
+431
-437
File diff suppressed because it is too large
Load Diff
+49
-49
@@ -35,25 +35,25 @@ class BeamlineDevices:
|
||||
logger.debug("initialising BEC worker")
|
||||
self.bec_worker = BECClientWorker(beamline)
|
||||
logger.debug("initialising BEC worker done")
|
||||
self.__smargon = smargon.Smargon(beamline)
|
||||
self._smargon = smargon.Smargon(beamline)
|
||||
self.exp_shutter = ExperimentalHutchShutter(beamline=self._beamline)
|
||||
# Personnel Safety System: gates whether the robot is allowed to move.
|
||||
self.pss = PssState(beamline=self._beamline)
|
||||
|
||||
# faster to define the dtz object here than in functions and then use
|
||||
self.__dtz = self.bec_worker.dev.det_z
|
||||
self._dtz = self.bec_worker.dev.det_z
|
||||
self.dtz_mod = cfg_get("daq.detector_distance_limit_modifier", 1.0)
|
||||
# TODO convert epics pvs to BEC
|
||||
self.__sample_cam = epicsAD(f"{BEAMLINE}-ES-MS:")
|
||||
self._sample_cam = epicsAD(f"{BEAMLINE}-ES-MS:")
|
||||
|
||||
self.__front_light = PredefinedPV(
|
||||
self._front_light = PredefinedPV(
|
||||
name="front_light",
|
||||
setpv=f"{BEAMLINE}-ES-FL:SET",
|
||||
getpv=f"{BEAMLINE}-ES-FL:SET",
|
||||
predefs={"off": 1.49, "half": 2.0, "max": 3.0},
|
||||
timeout=10.0,
|
||||
)
|
||||
self.__back_light = PredefinedPV(
|
||||
self._back_light = PredefinedPV(
|
||||
name="back_light",
|
||||
setpv=f"{BEAMLINE}-ES-BL:SET",
|
||||
getpv=f"{BEAMLINE}-ES-BL:SET",
|
||||
@@ -61,33 +61,33 @@ class BeamlineDevices:
|
||||
timeout=10.0,
|
||||
)
|
||||
|
||||
# self.__front_light = self.bec_worker.dev.fl_bright # need wrapper on bec_worker layer
|
||||
# self.__back_light = self.bec_worker.dev.bl_bright #need wrapper on bec_worker layer
|
||||
# self._front_light = self.bec_worker.dev.fl_bright # need wrapper on bec_worker layer
|
||||
# self._back_light = self.bec_worker.dev.bl_bright #need wrapper on bec_worker layer
|
||||
|
||||
self.__back_light_pos = EnumPV(
|
||||
self._back_light_pos = EnumPV(
|
||||
name="back_light_pos",
|
||||
setpv=f"{BEAMLINE}-ES-BL:POS-SET",
|
||||
getpv=f"{BEAMLINE}-ES-BL:POS-GET",
|
||||
timeout=10.0,
|
||||
)
|
||||
|
||||
self.__ringcurrent = self.bec_worker.ring_current
|
||||
self._ringcurrent = self.bec_worker.ring_current
|
||||
|
||||
self.__zoom = SetGetPV(
|
||||
self._zoom = SetGetPV(
|
||||
name="zoom", setpv=f"{BEAMLINE}-ES-MS:ZOOM.VAL", getpv=f"{BEAMLINE}-ES-MS:ZOOM.RBV"
|
||||
)
|
||||
|
||||
self.__cryojet_pos = EnumPV(
|
||||
self._cryojet_pos = EnumPV(
|
||||
name="cryojet_pos",
|
||||
setpv=f"{BEAMLINE}-ES-CS:POS-SET",
|
||||
getpv=f"{BEAMLINE}-ES-CS:POS-GET",
|
||||
timeout=10.0,
|
||||
)
|
||||
|
||||
self.__cryojet_x = MyMotor(f"{BEAMLINE}-ES-CS:TRX") # currently in is 5 out is 15?
|
||||
self._cryojet_x = MyMotor(f"{BEAMLINE}-ES-CS:TRX") # currently in is 5 out is 15?
|
||||
|
||||
self.__cryojet_temperature_get = PV(f"{BEAMLINE}-ES-CS:TEMP_RBV")
|
||||
self.__cryojet_temperature_set = PV(f"{BEAMLINE}-ES-CS:TEMP.VAL")
|
||||
self._cryojet_temperature_get = PV(f"{BEAMLINE}-ES-CS:TEMP_RBV")
|
||||
self._cryojet_temperature_set = PV(f"{BEAMLINE}-ES-CS:TEMP.VAL")
|
||||
|
||||
if self._beamline == MXBeamline.X06DA:
|
||||
self._transmission = SetGetPV(
|
||||
@@ -96,13 +96,13 @@ class BeamlineDevices:
|
||||
getpv=f"{BEAMLINE}-ES-BCFI:TRANSM-GET",
|
||||
)
|
||||
else:
|
||||
self.__transmission = SetGetPV(
|
||||
self._transmission = SetGetPV(
|
||||
name="transmission",
|
||||
setpv=f"{BEAMLINE}-ES-SSFI:TRANSM-SET",
|
||||
getpv=f"{BEAMLINE}-ES-SSFI:TRANSM-GET",
|
||||
)
|
||||
|
||||
self.__fast_shutter = PV(f"{BEAMLINE}-ES-SHUTTER:SET")
|
||||
self._fast_shutter = PV(f"{BEAMLINE}-ES-SHUTTER:SET")
|
||||
|
||||
self.magnet_position_sensor = PV(f"{BEAMLINE}-ES-DFS:CBOX-CMP1")
|
||||
self.magnet_position_sensor_readout = PV(f"{BEAMLINE}-ES-DFS:CBOX-USER1")
|
||||
@@ -134,7 +134,7 @@ class BeamlineDevices:
|
||||
def restart_smargon(self, simulated: bool = False) -> None:
|
||||
beamline = MXBeamline.SIMULATED if simulated else self._beamline
|
||||
logger.info(f"Restarting Smargon controller with simulated={simulated}")
|
||||
self.__smargon = smargon.Smargon(beamline)
|
||||
self._smargon = smargon.Smargon(beamline)
|
||||
|
||||
# Transmission
|
||||
@property
|
||||
@@ -152,38 +152,38 @@ class BeamlineDevices:
|
||||
# Lamp light
|
||||
@property
|
||||
def lamp_light(self) -> float:
|
||||
return self.__front_light.value
|
||||
return self._front_light.value
|
||||
|
||||
@lamp_light.setter
|
||||
def lamp_light(self, v: float):
|
||||
self.set_front_light(v, wait=False)
|
||||
|
||||
def set_front_light(self, v: float, /, wait: bool = True):
|
||||
self.__front_light.move(v, wait=wait)
|
||||
self._front_light.move(v, wait=wait)
|
||||
|
||||
# Back light
|
||||
@property
|
||||
def back_light(self) -> float:
|
||||
return self.__back_light.value
|
||||
return self._back_light.value
|
||||
|
||||
@back_light.setter
|
||||
def back_light(self, v: float):
|
||||
self.set_back_light(v, wait=False)
|
||||
|
||||
def set_back_light(self, v: float, /, wait: bool = True):
|
||||
self.__back_light.move(v, wait=wait)
|
||||
self._back_light.move(v, wait=wait)
|
||||
|
||||
# Zoom
|
||||
@property
|
||||
def zoom(self) -> float:
|
||||
return self.__zoom.value
|
||||
return self._zoom.value
|
||||
|
||||
@zoom.setter
|
||||
def zoom(self, value: float):
|
||||
self.set_zoom(value, wait=True)
|
||||
|
||||
def set_zoom(self, value: float, /, wait: bool = True):
|
||||
self.__zoom.move(value, wait=wait)
|
||||
self._zoom.move(value, wait=wait)
|
||||
|
||||
# Optics
|
||||
@property
|
||||
@@ -212,7 +212,7 @@ class BeamlineDevices:
|
||||
# Cryojet
|
||||
@property
|
||||
def cryojet_temp(self) -> float:
|
||||
return self.__cryojet_temperature_get.get()
|
||||
return self._cryojet_temperature_get.get()
|
||||
|
||||
def anneal(self, wait_time: float):
|
||||
logger.warning("Untested annealing procedure, use with caution")
|
||||
@@ -223,66 +223,66 @@ class BeamlineDevices:
|
||||
|
||||
@property
|
||||
def cryojet_pos(self) -> StagePositionEnum:
|
||||
return StagePositionEnum(self.__cryojet_pos.position.upper())
|
||||
return StagePositionEnum(self._cryojet_pos.position.upper())
|
||||
|
||||
@cryojet_pos.setter
|
||||
def cryojet_pos(self, value: StagePositionEnum):
|
||||
self.cryojet_pos_setter(value, wait=True)
|
||||
|
||||
def cryojet_pos_setter(self, value: StagePositionEnum, wait: bool = False):
|
||||
self.__cryojet_pos.move(value, wait=wait)
|
||||
self._cryojet_pos.move(value, wait=wait)
|
||||
|
||||
# Shutter
|
||||
@property
|
||||
def shutter(self) -> bool:
|
||||
return self.__fast_shutter.get()
|
||||
return self._fast_shutter.get()
|
||||
|
||||
@shutter.setter
|
||||
def shutter(self, opened: bool):
|
||||
self.__fast_shutter.put(opened)
|
||||
self._fast_shutter.put(opened)
|
||||
|
||||
# Sample camera
|
||||
@property
|
||||
def samcam_settings(self) -> SampleCameraSettings:
|
||||
return SampleCameraSettings(
|
||||
gain=self.__sample_cam.gain_rbv.value, exposure=self.__sample_cam.expo_rbv.value
|
||||
gain=self._sample_cam.gain_rbv.value, exposure=self._sample_cam.expo_rbv.value
|
||||
)
|
||||
|
||||
@samcam_settings.setter
|
||||
def samcam_settings(self, settings: SampleCameraSettings):
|
||||
self.__sample_cam.setup(settings.gain, settings.exposure)
|
||||
self._sample_cam.setup(settings.gain, settings.exposure)
|
||||
|
||||
@log_timing(logger, "auto exposure of camera")
|
||||
def samcam_auto(self, state: AutoEnum):
|
||||
self.__sample_cam.set_auto(state)
|
||||
self._sample_cam.set_auto(state)
|
||||
|
||||
def samcam_frame_id(self) -> int:
|
||||
"""
|
||||
Camera UniqueId for the last produced frame (monotonic counter from AreaDetector).
|
||||
"""
|
||||
return int(self.__sample_cam.uid.get())
|
||||
return int(self._sample_cam.uid.get())
|
||||
|
||||
# Detector Z
|
||||
@property
|
||||
def dtz(self) -> float:
|
||||
return self.__dtz.read()["det_z"]["value"]
|
||||
return self._dtz.read()["det_z"]["value"]
|
||||
|
||||
@dtz.setter
|
||||
def dtz(self, value: float):
|
||||
self.set_dtz(value, wait=True)
|
||||
|
||||
def set_dtz(self, value: float, /, wait: bool = True):
|
||||
if value < self.__dtz.low_limit:
|
||||
if value < self._dtz.low_limit:
|
||||
# raise ValueError(f"Detector distance cannot be less than {self.detector_distance_minimum} mm")
|
||||
logger.warning(
|
||||
f"Requested detector distance {value} is less than minimum: {self.__dtz.low_limit}, setting to minimum"
|
||||
f"Requested detector distance {value} is less than minimum: {self._dtz.low_limit}, setting to minimum"
|
||||
)
|
||||
value = self.__dtz.low_limit + self.dtz_mod
|
||||
if value > self.__dtz.high_limit:
|
||||
value = self._dtz.low_limit + self.dtz_mod
|
||||
if value > self._dtz.high_limit:
|
||||
logger.warning(
|
||||
f"Requested detector distance {value} is greater than maximum: {self.__dtz.high_limit}, setting to maximum"
|
||||
f"Requested detector distance {value} is greater than maximum: {self._dtz.high_limit}, setting to maximum"
|
||||
)
|
||||
value = self.__dtz.high_limit - self.dtz_mod
|
||||
value = self._dtz.high_limit - self.dtz_mod
|
||||
if wait:
|
||||
status = self.bec_worker.det_z(value, timeout=60)
|
||||
return status
|
||||
@@ -292,13 +292,13 @@ class BeamlineDevices:
|
||||
|
||||
@property
|
||||
def dtz_low(self) -> float:
|
||||
logger.info(f"dtz_low: {self.__dtz.low_limit}")
|
||||
return self.__dtz.low_limit
|
||||
logger.info(f"dtz_low: {self._dtz.low_limit}")
|
||||
return self._dtz.low_limit
|
||||
|
||||
@property
|
||||
def dtz_high(self) -> float:
|
||||
logger.info(f"dtz_high: {self.__dtz.high_limit}")
|
||||
return self.__dtz.high_limit
|
||||
logger.info(f"dtz_high: {self._dtz.high_limit}")
|
||||
return self._dtz.high_limit
|
||||
|
||||
@property
|
||||
def aerotech_pos(self) -> AerotechCoordinate:
|
||||
@@ -330,26 +330,26 @@ class BeamlineDevices:
|
||||
# Smargon goniometer
|
||||
@property
|
||||
def smargon_pos(self) -> SmargonCoordinate:
|
||||
return self.__smargon.readback
|
||||
return self._smargon.readback
|
||||
|
||||
def set_smargon_pos(self, pos: SmargonCoordinate, /, wait: bool = True):
|
||||
self.__smargon.target = pos
|
||||
self._smargon.target = pos
|
||||
|
||||
@smargon_pos.setter
|
||||
def smargon_pos(self, pos: SmargonCoordinate):
|
||||
self.set_smargon_pos(pos, wait=True)
|
||||
|
||||
def smargon_wait(self, timeout: float = 10.0):
|
||||
self.__smargon.wait(timeout=timeout)
|
||||
self._smargon.wait(timeout=timeout)
|
||||
|
||||
def smargon_move_home(self):
|
||||
self.__smargon.move_home(wait=True)
|
||||
self._smargon.move_home(wait=True)
|
||||
|
||||
def smargon_aerotech_wait(self):
|
||||
self.__smargon.wait_aerotech(timeout=10.0)
|
||||
self._smargon.wait_aerotech(timeout=10.0)
|
||||
|
||||
def smargon_initialize(self):
|
||||
self.__smargon.initialize()
|
||||
self._smargon.initialize()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -52,10 +52,10 @@ class MlBox:
|
||||
Optional injected inference wrapper for tests.
|
||||
If None, the production wrapper is created lazily.
|
||||
"""
|
||||
self.__beamline = bl
|
||||
self._beamline = bl
|
||||
|
||||
if wrapper is not None:
|
||||
self.__wrapper = wrapper
|
||||
self._wrapper = wrapper
|
||||
return
|
||||
|
||||
if bl == MXBeamline.SIMULATED:
|
||||
@@ -64,13 +64,13 @@ class MlBox:
|
||||
elif bl == MXBeamline.X06DA:
|
||||
from aarecommon.lc_infer_wrapper import AareLCInferWrapper
|
||||
|
||||
self.__wrapper = AareLCInferWrapper(bl)
|
||||
self._wrapper = AareLCInferWrapper(bl)
|
||||
|
||||
elif bl == MXBeamline.X10SA:
|
||||
# Lazy import keeps module importable without external client installed
|
||||
from aarecommon.lc_infer_wrapper import AareLCInferWrapper
|
||||
|
||||
self.__wrapper = AareLCInferWrapper(bl)
|
||||
self._wrapper = AareLCInferWrapper(bl)
|
||||
|
||||
elif bl == MXBeamline.X06SA:
|
||||
raise NotImplementedError(f"MLBox bundle mode not implemented for {bl}")
|
||||
@@ -120,7 +120,7 @@ class MlBox:
|
||||
last_error = None
|
||||
for attempt in range(1, self.RETRY_COUNT + 1):
|
||||
try:
|
||||
bundle = self.__wrapper.get_latest_prediction_bundle()
|
||||
bundle = self._wrapper.get_latest_prediction_bundle()
|
||||
logger.debug(f"Fetched prediction bundle on attempt {attempt}/{self.RETRY_COUNT}")
|
||||
return bundle
|
||||
except Exception as e:
|
||||
|
||||
+26
-68
@@ -3,7 +3,6 @@ import hmac
|
||||
import importlib
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import time
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import AsyncGenerator, Optional
|
||||
@@ -48,6 +47,8 @@ from fastapi.security import OAuth2PasswordBearer
|
||||
from starlette.responses import StreamingResponse
|
||||
from uvicorn.workers import UvicornWorker # deprecated shim, present in pinned 0.34.2
|
||||
|
||||
from aare.beamline_dispatch.beamline_dispatch import get_beamline_dispatch
|
||||
from aare.beamline_dispatch.protocols import BeamlineDispatch
|
||||
from aare.daq import auth
|
||||
from aare.daq.config import BeamlineConfig
|
||||
from aare.daq.config_model import LocalContactConfigModel
|
||||
@@ -60,9 +61,9 @@ logger = setup_logger("aareDAQ")
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
|
||||
|
||||
# ── Per-worker state: populated inside the lifespan, after fork ──
|
||||
bl = None
|
||||
cfg = None
|
||||
daq = None
|
||||
cfg: BeamlineConfig
|
||||
daq: AareDAQ
|
||||
bl_dispatch: BeamlineDispatch
|
||||
|
||||
_all_pgroups_cache: dict[str, tuple[list[str], float]] = {}
|
||||
_ALL_PGROUPS_TTL_S = 60.0 # adjust TTL as needed
|
||||
@@ -93,9 +94,11 @@ async def lifespan(application: FastAPI):
|
||||
All stateful / connection-opening initialisation belongs here so that
|
||||
each worker gets its own fresh Redis, BEC, EPICS, and TELL connections.
|
||||
"""
|
||||
global bl, cfg, daq
|
||||
await asyncio.sleep(random.uniform(0.5, 3.0))
|
||||
global cfg, daq, bl_dispatch
|
||||
|
||||
logger.info(f"Worker {os.getpid()} setting up JWT authentication...")
|
||||
bl_dispatch = get_beamline_dispatch()
|
||||
auth.init_jwt_key(bl_dispatch.auth)
|
||||
logger.info(f"Worker {os.getpid()} starting initialisation...")
|
||||
|
||||
# ── Core objects (Redis, EPICS PVs, BEC, TELL, JFJoch, etc.) ──
|
||||
@@ -203,9 +206,6 @@ def _get_automation_progress_state() -> dict:
|
||||
"""
|
||||
Read automation progress state from shared config/Redis storage.
|
||||
"""
|
||||
if cfg is None:
|
||||
return {"seq": 0, "progress": None}
|
||||
|
||||
try:
|
||||
return cfg.get_automation_progress_state()
|
||||
except Exception as e:
|
||||
@@ -220,9 +220,6 @@ def _push_automation_progress(progress: AutomationProgress) -> None:
|
||||
Args:
|
||||
progress: Current automation progress model.
|
||||
"""
|
||||
if cfg is None:
|
||||
return
|
||||
|
||||
try:
|
||||
state = cfg.set_automation_progress_state(progress)
|
||||
logger.info(
|
||||
@@ -371,7 +368,7 @@ async def sample_geometry(token: str = Depends(oauth2_scheme)) -> SampleGeometry
|
||||
|
||||
|
||||
@app.put("/beamline/omega")
|
||||
async def omega(val: float, token: str = Depends(oauth2_scheme)):
|
||||
async def set_omega_abs(val: float, token: str = Depends(oauth2_scheme)):
|
||||
"""
|
||||
Set the omega angle of the goniometer.
|
||||
|
||||
@@ -389,7 +386,7 @@ async def omega(val: float, token: str = Depends(oauth2_scheme)):
|
||||
|
||||
|
||||
@app.put("/beamline/omega_rel")
|
||||
async def omega(val: float, token: str = Depends(oauth2_scheme)):
|
||||
async def set_omega_rel(val: float, token: str = Depends(oauth2_scheme)):
|
||||
"""
|
||||
Move the omega angle relatively.
|
||||
|
||||
@@ -689,34 +686,34 @@ async def bec_save_current_aerotech_position(token: str = Depends(oauth2_scheme)
|
||||
|
||||
|
||||
def initialise_aerotech(self):
|
||||
self.__cfg.try_set_busy(timeout=360)
|
||||
self._cfg.try_set_busy(timeout=360)
|
||||
try:
|
||||
self.__devs.aerotech.home_aerotech()
|
||||
self.__cfg.state_busy = False
|
||||
self._devs.aerotech.home_aerotech()
|
||||
self._cfg.state_busy = False
|
||||
except Exception as e:
|
||||
self.__cfg.state_busy = False
|
||||
self._cfg.state_busy = False
|
||||
logger.error(f"Failed to initialise Aerotech: {e}")
|
||||
raise
|
||||
|
||||
|
||||
def detector_take_pedestal(self):
|
||||
self.__cfg.try_set_busy(timeout=360)
|
||||
self._cfg.try_set_busy(timeout=360)
|
||||
try:
|
||||
self.__jfjoch.take_pedestal()
|
||||
self.__cfg.state_busy = False
|
||||
self._jfjoch.take_pedestal()
|
||||
self._cfg.state_busy = False
|
||||
except Exception as e:
|
||||
self.__cfg.state_busy = False
|
||||
self._cfg.state_busy = False
|
||||
logger.error(f"Failed to take detector pedestal: {e}")
|
||||
raise
|
||||
|
||||
|
||||
def initialise_detector(self):
|
||||
self.__cfg.try_set_busy(timeout=360)
|
||||
self._cfg.try_set_busy(timeout=360)
|
||||
try:
|
||||
self.__jfjoch.initialize()
|
||||
self.__cfg.state_busy = False
|
||||
self._jfjoch.initialize()
|
||||
self._cfg.state_busy = False
|
||||
except Exception as e:
|
||||
self.__cfg.state_busy = False
|
||||
self._cfg.state_busy = False
|
||||
logger.error(f"Failed to initialise detector: {e}")
|
||||
raise
|
||||
|
||||
@@ -838,28 +835,6 @@ async def local_contact_set_config(
|
||||
return daq.set_local_contact_config(payload)
|
||||
|
||||
|
||||
@app.get("/local_contact/config")
|
||||
async def local_contact_config(token: str = Depends(oauth2_scheme)) -> LocalContactConfigModel:
|
||||
"""
|
||||
Return Local Contact config values. Staff only.
|
||||
"""
|
||||
data = auth.parse_token(token)
|
||||
auth.check_jwt_staff_only(data)
|
||||
return daq.get_local_contact_config()
|
||||
|
||||
|
||||
@app.put("/local_contact/config")
|
||||
async def local_contact_set_config(
|
||||
payload: LocalContactConfigModel, token: str = Depends(oauth2_scheme)
|
||||
) -> LocalContactConfigModel:
|
||||
"""
|
||||
Update Local Contact config values. Staff only.
|
||||
"""
|
||||
data = auth.parse_token(token)
|
||||
auth.check_jwt_staff_only(data)
|
||||
return daq.set_local_contact_config(payload)
|
||||
|
||||
|
||||
@app.post("/beamline/goto_abr_meas_pos")
|
||||
async def goto_abr_meas_pos(token: str = Depends(oauth2_scheme)):
|
||||
"""
|
||||
@@ -1344,7 +1319,7 @@ async def dewar_exchange(token: str = Depends(oauth2_scheme)):
|
||||
|
||||
|
||||
@app.post("/state/sample_exchange")
|
||||
async def dewar_exchange(token: str = Depends(oauth2_scheme)):
|
||||
async def sample_exchange(token: str = Depends(oauth2_scheme)):
|
||||
"""
|
||||
Transition beamline state to SampleExchange.
|
||||
|
||||
@@ -1435,7 +1410,7 @@ async def data_collection(token: str = Depends(oauth2_scheme)):
|
||||
|
||||
|
||||
@app.post("/state/robot_sample_exchange")
|
||||
async def dewar_exchange(token: str = Depends(oauth2_scheme)):
|
||||
async def robot_sample_exchange(token: str = Depends(oauth2_scheme)):
|
||||
"""
|
||||
Transition beamline state to SampleExchange.
|
||||
|
||||
@@ -1466,7 +1441,7 @@ async def xray_fluorescence(token: str = Depends(oauth2_scheme)):
|
||||
|
||||
|
||||
@app.post("/state/xtal_snapshot")
|
||||
async def beam_location(token: str = Depends(oauth2_scheme)):
|
||||
async def xtal_snapshot(token: str = Depends(oauth2_scheme)):
|
||||
"""
|
||||
Transition beamline state to BeamLocation. Staff only.
|
||||
|
||||
@@ -1477,23 +1452,6 @@ async def beam_location(token: str = Depends(oauth2_scheme)):
|
||||
daq.state = BeamlineStateEnum.XtalSnapshot
|
||||
|
||||
|
||||
@app.post("/state/maintenance")
|
||||
async def maintenance(token: str = Depends(oauth2_scheme)) -> str:
|
||||
"""
|
||||
Transition beamline state to Maintenance. Staff only.
|
||||
|
||||
Args:
|
||||
token: OAuth2 access token.
|
||||
|
||||
Returns:
|
||||
"OK" on success.
|
||||
"""
|
||||
auth.check_jwt_staff(cfg, auth.parse_token(token))
|
||||
cfg.state = BeamlineStateEnum.Maintenance
|
||||
logger.warning("Beamline state set to Maintenance via protected endpoint.")
|
||||
return "OK"
|
||||
|
||||
|
||||
@app.post("/access/take_over_beamline")
|
||||
async def take_over_beamline(
|
||||
payload: RecoveryActionRequest, token: str = Depends(oauth2_scheme)
|
||||
|
||||
@@ -27,7 +27,7 @@ def set_spreadsheet_in_redis(spreadsheet):
|
||||
"[REDIS][DEBUG] Data to write:", json.dumps(spreadsheet, indent=4)
|
||||
) # Pretty-print the data
|
||||
print("[REDIS][INFO] Writing spreadsheet to Redis...")
|
||||
config.__client.set(f"{config._BeamlineConfig__bl}:spreadsheet", json.dumps(spreadsheet))
|
||||
config.__client.set(f"{config._bl}:spreadsheet", json.dumps(spreadsheet))
|
||||
|
||||
|
||||
def on_message(ws, message):
|
||||
@@ -88,21 +88,21 @@ def on_message(ws, message):
|
||||
target_list.append(info)
|
||||
|
||||
# Write normal pucks to sample_spreadsheet
|
||||
normal_key = f"{config._BeamlineConfig__bl}:sample_spreadsheet"
|
||||
normal_key = f"{config._bl}:sample_spreadsheet"
|
||||
normal_list = SampleShortInfoList(s=normal_short_infos)
|
||||
config._BeamlineConfig__client.set(normal_key, normal_list.model_dump_json())
|
||||
config._client.set(normal_key, normal_list.model_dump_json())
|
||||
print("[REDIS][INFO] Written normal spreadsheet to:", normal_key)
|
||||
|
||||
# Write reference tools to reference-tools
|
||||
ref_key = f"{config._BeamlineConfig__bl}:reference-tools"
|
||||
ref_key = f"{config._bl}:reference-tools"
|
||||
if reference_short_infos:
|
||||
ref_list = SampleShortInfoList(s=reference_short_infos)
|
||||
config._BeamlineConfig__client.set(ref_key, ref_list.model_dump_json())
|
||||
config._client.set(ref_key, ref_list.model_dump_json())
|
||||
print("[REDIS][INFO] Written reference tools to:", ref_key)
|
||||
else:
|
||||
# Clear key if empty
|
||||
try:
|
||||
config._BeamlineConfig__client.delete(ref_key)
|
||||
config._client.delete(ref_key)
|
||||
print("[REDIS][INFO] Cleared reference tools key:", ref_key)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -98,8 +98,8 @@ def _get_redis_context() -> tuple[Any | None, str | None]:
|
||||
logger.debug("[REDIS] BeamlineConfig unavailable; skipping TELL redis write")
|
||||
return None, None
|
||||
|
||||
redis_client = getattr(config, "_BeamlineConfig__client", None)
|
||||
beamline_key = getattr(config, "_BeamlineConfig__bl", None)
|
||||
redis_client = getattr(config, "_client", None)
|
||||
beamline_key = getattr(config, "_bl", None)
|
||||
if redis_client is None or beamline_key is None:
|
||||
logger.error("[REDIS] BeamlineConfig internals unavailable; skipping TELL redis write")
|
||||
return None, None
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from typing import Optional, Union
|
||||
|
||||
from aarecommon.config.beamline import cfg_get, mx_beamline
|
||||
from aarecommon.config.logger import setup_logger
|
||||
from aarecommon.errors.exception_handler import AerotechCommunicationError
|
||||
from aarecommon.math.coordinate import AerotechCoordinate, Coordinate
|
||||
from aarecommon.models.beamline import MXBeamline
|
||||
@@ -18,7 +19,6 @@ from aarescan_client.models.screen_request import ScreenRequest
|
||||
|
||||
AEROTECH_HOME = AerotechCoordinate(at_mm=Coordinate(x=0, y=0, z=0), omega_deg=0)
|
||||
|
||||
from aarecommon.config.logger import setup_logger
|
||||
|
||||
logger = setup_logger("aareDAQ")
|
||||
|
||||
@@ -26,29 +26,29 @@ logger = setup_logger("aareDAQ")
|
||||
class AerotechController(object):
|
||||
def __init__(self, bl: MXBeamline):
|
||||
if bl == MXBeamline.X06DA:
|
||||
self.__simulated = False
|
||||
self.__base = cfg_get(
|
||||
self._simulated = False
|
||||
self._base = cfg_get(
|
||||
"daq.hardware.aerotech_url", "http://mx-x06da-queue-01.psi.ch:5234"
|
||||
)
|
||||
elif bl == MXBeamline.X10SA:
|
||||
self.__simulated = False
|
||||
self.__base = cfg_get(
|
||||
self._simulated = False
|
||||
self._base = cfg_get(
|
||||
"daq.hardware.aerotech_url", "http://mx-x10sa-queue-01.psi.ch:5234"
|
||||
)
|
||||
elif bl == MXBeamline.X06SA:
|
||||
raise NotImplementedError("Not implemented aerotech url for X06SA")
|
||||
elif bl == MXBeamline.SIMULATED:
|
||||
self.__simulated = True
|
||||
self.__pos = AEROTECH_HOME
|
||||
self.__vel = 0
|
||||
self._simulated = True
|
||||
self._pos = AEROTECH_HOME
|
||||
self._vel = 0
|
||||
else:
|
||||
raise Exception("unknown beamline")
|
||||
|
||||
if not self.__simulated:
|
||||
self.__client = ApiClient(Configuration(host=self.__base))
|
||||
self.__api = DefaultApi(self.__client)
|
||||
if not self._simulated:
|
||||
self._client = ApiClient(Configuration(host=self._base))
|
||||
self._api = DefaultApi(self._client)
|
||||
|
||||
def __make_aerotech_target(
|
||||
def _make_aerotech_target(
|
||||
self, coord: AerotechCoordinate, wait: bool = False, incremental: bool = False
|
||||
) -> Target:
|
||||
at_mm = coord.at_mm
|
||||
@@ -62,31 +62,31 @@ class AerotechController(object):
|
||||
incremental=incremental,
|
||||
)
|
||||
|
||||
def __make_aerotech_coordinate(self, target: Target) -> AerotechCoordinate:
|
||||
def _make_aerotech_coordinate(self, target: Target) -> AerotechCoordinate:
|
||||
return AerotechCoordinate(
|
||||
at_mm=Coordinate(x=target.x, y=target.y, z=target.z), omega_deg=target.u
|
||||
)
|
||||
|
||||
def cancel(self):
|
||||
try:
|
||||
self.__api.cancel_post()
|
||||
self._api.cancel_post()
|
||||
except Exception as e:
|
||||
raise AerotechCommunicationError(
|
||||
"Aerotech cancel failed",
|
||||
endpoint="cancel_post",
|
||||
base_url=self.__base,
|
||||
base_url=self._base,
|
||||
operation="POST",
|
||||
) from e
|
||||
|
||||
def is_idle(self) -> bool:
|
||||
try:
|
||||
status = self.__api.status_get()
|
||||
status = self._api.status_get()
|
||||
return status.state == "Idle"
|
||||
except Exception as e:
|
||||
raise AerotechCommunicationError(
|
||||
"Aerotech status check failed",
|
||||
endpoint="status_get",
|
||||
base_url=self.__base,
|
||||
base_url=self._base,
|
||||
operation="GET",
|
||||
) from e
|
||||
|
||||
@@ -97,36 +97,36 @@ class AerotechController(object):
|
||||
)
|
||||
|
||||
def status(self) -> Status:
|
||||
if self.__simulated:
|
||||
if self._simulated:
|
||||
return Status(
|
||||
state=Status.State.IDLE,
|
||||
x=AxisStatus(
|
||||
pos=self.__pos.x,
|
||||
vel=self.__vel,
|
||||
pos=self._pos.x,
|
||||
vel=self._vel,
|
||||
enabled=False,
|
||||
homed=False,
|
||||
moving=False,
|
||||
fault=False,
|
||||
),
|
||||
y=AxisStatus(
|
||||
pos=self.__pos.y,
|
||||
vel=self.__vel,
|
||||
pos=self._pos.y,
|
||||
vel=self._vel,
|
||||
enabled=False,
|
||||
homed=False,
|
||||
moving=False,
|
||||
fault=False,
|
||||
),
|
||||
z=AxisStatus(
|
||||
pos=self.__pos.z,
|
||||
vel=self.__vel,
|
||||
pos=self._pos.z,
|
||||
vel=self._vel,
|
||||
enabled=False,
|
||||
homed=False,
|
||||
moving=False,
|
||||
fault=False,
|
||||
),
|
||||
u=AxisStatus(
|
||||
pos=self.__pos.u,
|
||||
vel=self.__vel,
|
||||
pos=self._pos.u,
|
||||
vel=self._vel,
|
||||
enabled=False,
|
||||
homed=False,
|
||||
moving=False,
|
||||
@@ -134,52 +134,52 @@ class AerotechController(object):
|
||||
),
|
||||
)
|
||||
try:
|
||||
return self.__api.status_get()
|
||||
return self._api.status_get()
|
||||
except Exception as e:
|
||||
raise AerotechCommunicationError(
|
||||
"Aerotech status request failed",
|
||||
endpoint="status_get",
|
||||
base_url=self.__base,
|
||||
base_url=self._base,
|
||||
operation="GET",
|
||||
) from e
|
||||
|
||||
def move_home(self, wait: bool = True, incremental: bool = False):
|
||||
if self.__simulated:
|
||||
self.__pos = AEROTECH_HOME
|
||||
return self.__pos
|
||||
if self._simulated:
|
||||
self._pos = AEROTECH_HOME
|
||||
return self._pos
|
||||
return self.position(AEROTECH_HOME, wait=wait, incremental=incremental)
|
||||
|
||||
def home_aerotech(self):
|
||||
try:
|
||||
return self.__api.home_post()
|
||||
return self._api.home_post()
|
||||
except Exception as e:
|
||||
raise AerotechCommunicationError(
|
||||
"Aerotech home failed", endpoint="home_post", base_url=self.__base, operation="POST"
|
||||
"Aerotech home failed", endpoint="home_post", base_url=self._base, operation="POST"
|
||||
) from e
|
||||
|
||||
def wait_till_done(self, timeout=60):
|
||||
try:
|
||||
return self.__api.wait_till_done_post(timeout=timeout)
|
||||
return self._api.wait_till_done_post(timeout=timeout)
|
||||
except Exception as e:
|
||||
raise AerotechCommunicationError(
|
||||
"Aerotech wait_till_done failed",
|
||||
endpoint="wait_till_done_post",
|
||||
base_url=self.__base,
|
||||
base_url=self._base,
|
||||
operation="POST",
|
||||
) from e
|
||||
|
||||
def position(self, target: AerotechCoordinate, /, wait: bool = True, incremental: bool = False):
|
||||
if self.__simulated:
|
||||
return self.__pos
|
||||
if self._simulated:
|
||||
return self._pos
|
||||
|
||||
payload = self.__make_aerotech_target(target, wait=wait, incremental=incremental)
|
||||
payload = self._make_aerotech_target(target, wait=wait, incremental=incremental)
|
||||
try:
|
||||
return self.__api.position_post(payload)
|
||||
return self._api.position_post(payload)
|
||||
except Exception as e:
|
||||
raise AerotechCommunicationError(
|
||||
"Aerotech position move failed",
|
||||
endpoint="position_post",
|
||||
base_url=self.__base,
|
||||
base_url=self._base,
|
||||
operation="POST",
|
||||
) from e
|
||||
|
||||
@@ -196,16 +196,16 @@ class AerotechController(object):
|
||||
start_pos_deg=start_pos_deg,
|
||||
run_async=run_async,
|
||||
)
|
||||
if self.__simulated:
|
||||
if self._simulated:
|
||||
return payload
|
||||
|
||||
try:
|
||||
return self.__api.rotation_scan_post(payload)
|
||||
return self._api.rotation_scan_post(payload)
|
||||
except Exception as e:
|
||||
raise AerotechCommunicationError(
|
||||
"Aerotech rotation scan failed",
|
||||
endpoint="rotation_scan_post",
|
||||
base_url=self.__base,
|
||||
base_url=self._base,
|
||||
operation="POST",
|
||||
) from e
|
||||
|
||||
@@ -226,15 +226,15 @@ class AerotechController(object):
|
||||
time_sec=time_sec,
|
||||
run_async=run_async,
|
||||
)
|
||||
if self.__simulated:
|
||||
if self._simulated:
|
||||
return payload
|
||||
try:
|
||||
return self.__api.grid_scan_post(payload)
|
||||
return self._api.grid_scan_post(payload)
|
||||
except Exception as e:
|
||||
raise AerotechCommunicationError(
|
||||
"Aerotech grid scan failed",
|
||||
endpoint="grid_scan_post",
|
||||
base_url=self.__base,
|
||||
base_url=self._base,
|
||||
operation="POST",
|
||||
) from e
|
||||
|
||||
@@ -253,16 +253,16 @@ class AerotechController(object):
|
||||
steps=steps,
|
||||
run_async=run_async,
|
||||
)
|
||||
if self.__simulated:
|
||||
if self._simulated:
|
||||
return payload
|
||||
try:
|
||||
logger.info("sending screening scan request to aerotech")
|
||||
return self.__api.screening_post(payload)
|
||||
return self._api.screening_post(payload)
|
||||
except Exception as e:
|
||||
raise AerotechCommunicationError(
|
||||
"Aerotech screening scan failed",
|
||||
endpoint="screening_post",
|
||||
base_url=self.__base,
|
||||
base_url=self._base,
|
||||
operation="POST",
|
||||
) from e
|
||||
|
||||
|
||||
@@ -57,14 +57,15 @@ class epicsAD(object):
|
||||
self.gain_max = epics.PV(prefix + cam + "GC_AutGainUpperLimit")
|
||||
self.exp_max = epics.PV(prefix + cam + "GC_AutExpTimUppLimit")
|
||||
self.auto_white_balance = epics.PV(prefix + cam + "GC_BalanceWhiteAuto")
|
||||
self.__init_auto_exp()
|
||||
self._init_auto_exp()
|
||||
|
||||
try:
|
||||
epics.ca.pend_io()
|
||||
except:
|
||||
except Exception as e:
|
||||
print(f"EPICS error: {e}")
|
||||
pass
|
||||
|
||||
def __init_auto_exp(self, settings: AutoExposureSettings = AutoExposureSettings()):
|
||||
def _init_auto_exp(self, settings: AutoExposureSettings = AutoExposureSettings()):
|
||||
self.acquire.put(0)
|
||||
self.aoi_start_x.put(settings.aoi_offset_x)
|
||||
self.aoi_start_y.put(settings.aoi_offset_y)
|
||||
|
||||
@@ -13,19 +13,10 @@ from bec_lib.device import RPCError, ScanRequestError
|
||||
from bec_lib.procedures.helper import FrontendProcedureHelper
|
||||
from bec_lib.service_config import ServiceConfig
|
||||
|
||||
from aare.beamline_dispatch.beamline_dispatch import get_beamline_dispatch
|
||||
|
||||
logger = setup_logger("aareDAQ")
|
||||
|
||||
# specify up to 10 queue to runs in parallel, request more if needed!
|
||||
# st = client.proc.request_new("sleep", ((), {"time_s":5}), queue="test")
|
||||
|
||||
# to see all deevices
|
||||
# devs.show_all
|
||||
|
||||
# helper fucntions
|
||||
# helper.get.active_and_pending_queue_names()
|
||||
# helper.get.running_procedures()
|
||||
# helper.request.abort_queue()
|
||||
|
||||
|
||||
class DetectorCoverEnum(str, Enum):
|
||||
"""Enum for the detector cover position
|
||||
@@ -73,6 +64,8 @@ class BECClientWorker:
|
||||
else:
|
||||
raise ValueError(f"Unknown beamline: {beamline}")
|
||||
|
||||
self.dispatch = get_beamline_dispatch()
|
||||
|
||||
if self.beamline is MXBeamline.SIMULATED:
|
||||
self.simulated = True
|
||||
|
||||
@@ -90,37 +83,37 @@ class BECClientWorker:
|
||||
self.dev = self.client.device_manager.devices
|
||||
print(self.dev.keys())
|
||||
self.scans = self.client.scans
|
||||
self.macros = self.client.macros
|
||||
self.__load_user_macros()
|
||||
print(self.__list_all_macros())
|
||||
self.macros = self.dispatch.bec_macros
|
||||
self.client.macros._load_user_macros()
|
||||
print(self._list_all_macros())
|
||||
self.helper = FrontendProcedureHelper(self.client.connector)
|
||||
self.__set_scilog_tags()
|
||||
self._set_scilog_tags()
|
||||
try:
|
||||
self.__init_beamline_environment()
|
||||
self._init_beamline_environment()
|
||||
except Exception as e:
|
||||
logger.error(f"Error initialising BEC devices: {e}")
|
||||
exit(1)
|
||||
logger.debug(f"simulated is {self.simulated}")
|
||||
|
||||
def __init_beamline_environment(self):
|
||||
def _init_beamline_environment(self):
|
||||
try:
|
||||
self.position_devices, self.planner = init_beamline_environment()
|
||||
self.__backlight_brightness = self.position_devices["bl_bright"]
|
||||
self.__frontlight_brightness = self.position_devices["fl_bright"]
|
||||
self.__zoom = self.dev.scam_zoom
|
||||
self.position_devices, self.planner = self.macros.init_beamline_environment()
|
||||
self._backlight_brightness = self.position_devices["bl_bright"]
|
||||
self._frontlight_brightness = self.position_devices["fl_bright"]
|
||||
self._zoom = self.dev.scam_zoom
|
||||
self._ring_current = self.dev.sls_current
|
||||
except Exception as e:
|
||||
logger.error(f"Error initialising planar and position devices: {e}")
|
||||
self.position_devices = None
|
||||
self.planner = None
|
||||
self.__backlight_brightness = None
|
||||
self.__frontlight_brightness = None
|
||||
self._backlight_brightness = None
|
||||
self._frontlight_brightness = None
|
||||
try:
|
||||
self.__zoom = self.dev.scam_zoom
|
||||
self._zoom = self.dev.scam_zoom
|
||||
self._ring_current = self.dev.sls_current
|
||||
except Exception as e:
|
||||
logger.error(f"Error initialising zoom and ring_current: {e}")
|
||||
self.__zoom = None
|
||||
self._zoom = None
|
||||
self.ring_current = None
|
||||
raise Exception(f"Error initialising BEC devices: {e}")
|
||||
|
||||
@@ -163,7 +156,7 @@ class BECClientWorker:
|
||||
|
||||
raise BECCommunicationError(message, operation=operation, exception=exc) from exc
|
||||
|
||||
def __set_scilog_tags(self, tags: Optional[List[str]] = None):
|
||||
def _set_scilog_tags(self, tags: Optional[List[str]] = None):
|
||||
try:
|
||||
if tags:
|
||||
self.client.messaging.scilog.set_default_tags(tags)
|
||||
@@ -298,7 +291,7 @@ class BECClientWorker:
|
||||
logger.debug("Simulating list_all_user_macros")
|
||||
return []
|
||||
try:
|
||||
self.__list_all_macros()
|
||||
self._list_all_macros()
|
||||
raw_macros = [name for name, _ in self.client.macros._update_handler.macros.items()]
|
||||
if raw_macros is None:
|
||||
logger.warning("BEC returned no user macros; treating as empty list")
|
||||
@@ -307,8 +300,8 @@ class BECClientWorker:
|
||||
except Exception as e:
|
||||
self._raise_bec_error(e, operation="list_all_user_macros")
|
||||
|
||||
def __list_all_macros(self):
|
||||
result = self.macros.list_user_macros()
|
||||
def _list_all_macros(self):
|
||||
result = self.client.macros.list_user_macros()
|
||||
if result is None:
|
||||
return []
|
||||
return result
|
||||
@@ -318,11 +311,11 @@ class BECClientWorker:
|
||||
logger.debug("Simulating load_user_macros")
|
||||
return None
|
||||
try:
|
||||
return self.__load_user_macros()
|
||||
return self._load_user_macros()
|
||||
except Exception as e:
|
||||
self._raise_bec_error(e, operation="load_user_macros")
|
||||
|
||||
def __load_user_macros(self):
|
||||
def _load_user_macros(self):
|
||||
result = self.macros.load_all_user_macros()
|
||||
if result is None:
|
||||
logger.warning("BEC load_all_user_macros returned None")
|
||||
@@ -348,7 +341,7 @@ class BECClientWorker:
|
||||
self.client.config.update_session_with_file(
|
||||
f"/sls/{self.beamline}/config/bec/production/{self._beamline_name}_bec/{self._beamline_name}_bec/device_configs/{self._beamline_name}-devices.yaml"
|
||||
)
|
||||
self.__init_beamline_environment()
|
||||
self._init_beamline_environment()
|
||||
logger.info(f"Reinitialised BEC planner and position devices using method={method}")
|
||||
return self.list_position_devices()
|
||||
except Exception as e:
|
||||
@@ -361,7 +354,7 @@ class BECClientWorker:
|
||||
|
||||
def mono_pitch_scan_runner(self, plot: bool = False):
|
||||
try:
|
||||
mono_pitch_scan(plot)
|
||||
self.macros.mono_pitch_scan(plot)
|
||||
except Exception as e:
|
||||
self._raise_bec_error(e, operation="mono_pitch_scan", tags=["mono_pitch_scan"])
|
||||
if self.beamline is MXBeamline.X06DA:
|
||||
@@ -378,7 +371,7 @@ class BECClientWorker:
|
||||
|
||||
def check_current_energy(self):
|
||||
"""Get the current energy from the BEC in eV"""
|
||||
energy_ev = get_current_energy()
|
||||
energy_ev = self.macros.get_current_energy()
|
||||
energy_kev = energy_ev / 1000
|
||||
return energy_kev
|
||||
|
||||
@@ -387,7 +380,7 @@ class BECClientWorker:
|
||||
logger.info(f"Current energy: {current_energy:.1f} eV")
|
||||
logger.info(f"Change energy requested: from {current_energy:.1f} to {value:.1f} eV")
|
||||
try:
|
||||
bl_energy(value, move_gap=False, mono_scan=True, plot=plot)
|
||||
self.macros.bl_energy(value, move_gap=False, mono_scan=True, plot=plot)
|
||||
except Exception as e:
|
||||
self._raise_bec_error(
|
||||
e,
|
||||
@@ -460,7 +453,7 @@ class BECClientWorker:
|
||||
"""returns backlight brightness as an Enum: 'off' or 'on', can also be a value...
|
||||
How to handle"""
|
||||
try:
|
||||
return BrightnessEnum(self.__backlight_brightness.actual)
|
||||
return BrightnessEnum(self._backlight_brightness.actual)
|
||||
except Exception as e:
|
||||
self._raise_bec_error(
|
||||
e,
|
||||
@@ -475,14 +468,14 @@ class BECClientWorker:
|
||||
if self.simulated:
|
||||
return
|
||||
try:
|
||||
self.__backlight_brightness.move(value)
|
||||
self._backlight_brightness.move(value)
|
||||
except Exception as e:
|
||||
self._raise_bec_error(e, operation=f"backlight_brightness:{value}", tags=["backlight"])
|
||||
raise
|
||||
|
||||
def get_backlight_pos(self) -> BrightnessEnum:
|
||||
"""Returns the current backlight brightness position"""
|
||||
return BrightnessEnum(self.__backlight_brightness.pos)
|
||||
return BrightnessEnum(self._backlight_brightness.pos)
|
||||
|
||||
def backlight_toggle(self):
|
||||
"""Turn the backlight on or off"""
|
||||
@@ -503,28 +496,28 @@ class BECClientWorker:
|
||||
)
|
||||
|
||||
def save_current_bs_pos(self):
|
||||
save_current_position(self.dev.bs_z, "safe")
|
||||
self.macros.save_current_position(self.dev.bs_z, "safe")
|
||||
|
||||
def save_current_collimator_pos(self):
|
||||
save_current_position(self.dev.coll_y, "work")
|
||||
self.macros.save_current_position(self.dev.coll_y, "work")
|
||||
|
||||
def save_current_aerotech_position(self):
|
||||
save_current_position(self.dev.aerotech, "work", axis="x")
|
||||
save_current_position(self.dev.aerotech, "work", axis="y")
|
||||
save_current_position(self.dev.aerotech, "work", axis="z")
|
||||
self.macros.save_current_position(self.dev.aerotech, "work", axis="x")
|
||||
self.macros.save_current_position(self.dev.aerotech, "work", axis="y")
|
||||
self.macros.save_current_position(self.dev.aerotech, "work", axis="z")
|
||||
|
||||
self.save_config_and_reload_devices()
|
||||
|
||||
def save_config_and_reload_devices(self):
|
||||
self.position_devices, self.planner = save_and_reload()
|
||||
self.position_devices, self.planner = self.macros.save_and_reload()
|
||||
|
||||
@property
|
||||
def zoom(self):
|
||||
return self.__zoom.position
|
||||
return self._zoom.position
|
||||
|
||||
@zoom.setter
|
||||
def zoom(self, value: float):
|
||||
self.scans.umv(self.__zoom, value, relative=False)
|
||||
self.scans.umv(self._zoom, value, relative=False)
|
||||
|
||||
@property
|
||||
def ring_current(self) -> float:
|
||||
|
||||
@@ -5,12 +5,12 @@ from epics import PV
|
||||
class ExperimentalHutchShutter:
|
||||
def __init__(self, beamline: MXBeamline):
|
||||
BEAMLINE = beamline.value.upper()
|
||||
self.__close = PV(f"{BEAMLINE}-EH1-PSYS:SH-A-CLOSE-SET") # 1, 0
|
||||
self.__open = PV(f"{BEAMLINE}-EH1-PSYS:SH-A-OPEN-SET")
|
||||
self.__state = PV(f"{BEAMLINE}-OP-PSH1-EMLS-0010:OPEN")
|
||||
self._close = PV(f"{BEAMLINE}-EH1-PSYS:SH-A-CLOSE-SET") # 1, 0
|
||||
self._open = PV(f"{BEAMLINE}-EH1-PSYS:SH-A-OPEN-SET")
|
||||
self._state = PV(f"{BEAMLINE}-OP-PSH1-EMLS-0010:OPEN")
|
||||
|
||||
def state(self):
|
||||
state = self.__state.get()
|
||||
state = self._state.get()
|
||||
if state == "Open" or state == 1:
|
||||
return True
|
||||
elif state == "Not Open" or state == 0:
|
||||
@@ -20,11 +20,11 @@ class ExperimentalHutchShutter:
|
||||
return False
|
||||
|
||||
def open(self):
|
||||
self.__open.put(0)
|
||||
self.__open.put(1)
|
||||
# print(self.__open.get())
|
||||
self.__open.put(0)
|
||||
self._open.put(0)
|
||||
self._open.put(1)
|
||||
# print(self._open.get())
|
||||
self._open.put(0)
|
||||
|
||||
def close(self):
|
||||
self.__close.put(1)
|
||||
self.__close.put(0)
|
||||
self._close.put(1)
|
||||
self._close.put(0)
|
||||
|
||||
@@ -11,18 +11,18 @@ class FilterTransmission:
|
||||
self._fail_on_timeout = fail_on_timeout
|
||||
self._timeout = timeout
|
||||
|
||||
self.__set = PV(f"{BEAMLINE}-ES-FI:TRANSM-SET")
|
||||
self.__get = PV(f"{BEAMLINE}-ES-FI:TRANSM-GET")
|
||||
self.__done = PV(f"{BEAMLINE}-ES-FI:DONE")
|
||||
self._set = PV(f"{BEAMLINE}-ES-FI:TRANSM-SET")
|
||||
self._get = PV(f"{BEAMLINE}-ES-FI:TRANSM-GET")
|
||||
self._done = PV(f"{BEAMLINE}-ES-FI:DONE")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<FilterTransmission: {self.__set.get():.4f}>"
|
||||
return f"<FilterTransmission: {self._set.get():.4f}>"
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.__get.get():.4f}"
|
||||
return f"{self._get.get():.4f}"
|
||||
|
||||
def is_done(self):
|
||||
return 1 == int(self.__done.value)
|
||||
return 1 == int(self._done.value)
|
||||
|
||||
def is_busy(self):
|
||||
return not self.is_done()
|
||||
@@ -33,7 +33,7 @@ class FilterTransmission:
|
||||
raise RuntimeError(
|
||||
f"requested transmission ({val:.4f}) is out of bounds --- valid range : 0 <= trans <= 1.0"
|
||||
)
|
||||
self.__set.put(val)
|
||||
self._set.put(val)
|
||||
poll(0.2)
|
||||
if wait:
|
||||
self.wait()
|
||||
@@ -41,7 +41,7 @@ class FilterTransmission:
|
||||
def get(self) -> float | None:
|
||||
if self.is_busy():
|
||||
return None
|
||||
return round(self.__get.value, 5)
|
||||
return round(self._get.value, 5)
|
||||
|
||||
def wait(self):
|
||||
timeisup = False
|
||||
|
||||
@@ -11,38 +11,38 @@ class Fluorimeter(object):
|
||||
def __init__(self, beamline: MXBeamline, **kwargs):
|
||||
BEAMLINE = beamline.value.upper()
|
||||
|
||||
self.__start = PV(f"{BEAMLINE}-ES-SiD:mca1Start") # 0 done ,1 start
|
||||
self.__stop = PV(f"{BEAMLINE}-ES-SiD:mca1Stop") # 0 done ,1 stop
|
||||
self.__erase_and_start = PV(f"{BEAMLINE}-ES-SiD:mca1EraseStart") # 0 done,1 start
|
||||
self.__erase = PV(f"{BEAMLINE}-ES-SiD:mca1Erase") # 0 done,1 erase
|
||||
self._start = PV(f"{BEAMLINE}-ES-SiD:mca1Start") # 0 done ,1 start
|
||||
self._stop = PV(f"{BEAMLINE}-ES-SiD:mca1Stop") # 0 done ,1 stop
|
||||
self._erase_and_start = PV(f"{BEAMLINE}-ES-SiD:mca1EraseStart") # 0 done,1 start
|
||||
self._erase = PV(f"{BEAMLINE}-ES-SiD:mca1Erase") # 0 done,1 erase
|
||||
|
||||
self.__preset_mode = PV(f"{BEAMLINE}-ES-SiD:dxp1:PresetMode") # set mode
|
||||
self.__status = PV(f"{BEAMLINE}-ES-SiD:mca1.ACQG") # 0 done, 1 acquire
|
||||
self._preset_mode = PV(f"{BEAMLINE}-ES-SiD:dxp1:PresetMode") # set mode
|
||||
self._status = PV(f"{BEAMLINE}-ES-SiD:mca1.ACQG") # 0 done, 1 acquire
|
||||
|
||||
self.__real_time = PV(f"{BEAMLINE}-ES-SiD:mca1.PRTM") # float
|
||||
self.__live_time = PV(f"{BEAMLINE}-ES-SiD:mca1.PLTM") # float
|
||||
self.__elapsed_real_time = PV(f"{BEAMLINE}-ES-SiD:mca1.ERTM") # float
|
||||
self.__elapsed_live_time = PV(f"{BEAMLINE}-ES-SiD:mca1.ELTM") # float
|
||||
self.__elapsed_trigger_live_time = PV(f"{BEAMLINE}-ES-SiD:dxp1:ElapsedTriggerLiveTime")
|
||||
self._real_time = PV(f"{BEAMLINE}-ES-SiD:mca1.PRTM") # float
|
||||
self._live_time = PV(f"{BEAMLINE}-ES-SiD:mca1.PLTM") # float
|
||||
self._elapsed_real_time = PV(f"{BEAMLINE}-ES-SiD:mca1.ERTM") # float
|
||||
self._elapsed_live_time = PV(f"{BEAMLINE}-ES-SiD:mca1.ELTM") # float
|
||||
self._elapsed_trigger_live_time = PV(f"{BEAMLINE}-ES-SiD:dxp1:ElapsedTriggerLiveTime")
|
||||
|
||||
self.__instant_dead_time = PV(f"{BEAMLINE}-ES-SiD:mca1.IDTIM")
|
||||
self.__average_dead_time = PV(f"{BEAMLINE}-ES-SiD:mca1.DTIM")
|
||||
self._instant_dead_time = PV(f"{BEAMLINE}-ES-SiD:mca1.IDTIM")
|
||||
self._average_dead_time = PV(f"{BEAMLINE}-ES-SiD:mca1.DTIM")
|
||||
|
||||
self.__poll_time_rbv = PV(f"{BEAMLINE}-ES-SiD:PollTime_RBV")
|
||||
self.__poll_time = PV(f"{BEAMLINE}-ES-SiD:PollTime")
|
||||
self._poll_time_rbv = PV(f"{BEAMLINE}-ES-SiD:PollTime_RBV")
|
||||
self._poll_time = PV(f"{BEAMLINE}-ES-SiD:PollTime")
|
||||
|
||||
self.__mca = PV(f"{BEAMLINE}-ES-SiD:mca1")
|
||||
self.__data = PV(f"{BEAMLINE}-ES-SiD:mca1.VAL")
|
||||
self.__background = PV(f"{BEAMLINE}-ES-SiD:mca1.BG")
|
||||
self._mca = PV(f"{BEAMLINE}-ES-SiD:mca1")
|
||||
self._data = PV(f"{BEAMLINE}-ES-SiD:mca1.VAL")
|
||||
self._background = PV(f"{BEAMLINE}-ES-SiD:mca1.BG")
|
||||
|
||||
self.__saveFile = PV(f"{BEAMLINE}-ES-SiD:SaveSystemFile")
|
||||
self.__saveFile_name = PV(f"{BEAMLINE}-ES-SiD:SaveSystem")
|
||||
self.__saveFile_rbv = PV(f"{BEAMLINE}-ES-SiD:SaveSystem_RBV") # 1 ssave, 0 done
|
||||
self.__roi_1 = self.get_roi(BEAMLINE, 1) # currently SiEsc
|
||||
self.__roi_2 = self.get_roi(BEAMLINE, 2) # currently MnKa
|
||||
self._saveFile = PV(f"{BEAMLINE}-ES-SiD:SaveSystemFile")
|
||||
self._saveFile_name = PV(f"{BEAMLINE}-ES-SiD:SaveSystem")
|
||||
self._saveFile_rbv = PV(f"{BEAMLINE}-ES-SiD:SaveSystem_RBV") # 1 ssave, 0 done
|
||||
self._roi_1 = self.get_roi(BEAMLINE, 1) # currently SiEsc
|
||||
self._roi_2 = self.get_roi(BEAMLINE, 2) # currently MnKa
|
||||
|
||||
self.__calibration_offset = PV(f"{BEAMLINE}-ES-SiD:mca1.CALO")
|
||||
self.__calibration_slope = PV(f"{BEAMLINE}-ES-SiD:mca1.CALS")
|
||||
self._calibration_offset = PV(f"{BEAMLINE}-ES-SiD:mca1.CALO")
|
||||
self._calibration_slope = PV(f"{BEAMLINE}-ES-SiD:mca1.CALS")
|
||||
|
||||
def get_roi(self, bl, roi_num: int):
|
||||
if roi_num not in [1, 2]:
|
||||
@@ -52,15 +52,15 @@ class Fluorimeter(object):
|
||||
|
||||
def start_acquisition(self, erase: bool = False):
|
||||
if erase:
|
||||
self.__erase_and_start.put(1)
|
||||
self._erase_and_start.put(1)
|
||||
else:
|
||||
self.__start.put(1)
|
||||
self._start.put(1)
|
||||
|
||||
def stop_acquisition(self):
|
||||
self.__stop.put(1)
|
||||
self._stop.put(1)
|
||||
|
||||
def erase(self):
|
||||
self.__erase.put(1)
|
||||
self._erase.put(1)
|
||||
|
||||
def poll_data(self, timeout_s: float = 30.0):
|
||||
timeout = time.time() + timeout_s
|
||||
@@ -68,17 +68,17 @@ class Fluorimeter(object):
|
||||
if time.time() > timeout:
|
||||
self.stop_acquisition()
|
||||
raise TimeoutError("timeout waiting for data to be acquired.")
|
||||
self.__mca.get()
|
||||
self._mca.get()
|
||||
poll(0.1)
|
||||
|
||||
def get_current_data(self):
|
||||
return self.__data.get()
|
||||
return self._data.get()
|
||||
|
||||
def get_current_background(self):
|
||||
return self.__background.get()
|
||||
return self._background.get()
|
||||
|
||||
def check_status(self):
|
||||
return self.__status.get()
|
||||
return self._status.get()
|
||||
|
||||
def check_status_done(self):
|
||||
if self.check_status() == 0:
|
||||
@@ -107,18 +107,18 @@ class Fluorimeter(object):
|
||||
if mode not in [0, 1, 2, 3, 4]:
|
||||
raise ValueError(f"Invalid preset mode: {mode}")
|
||||
try:
|
||||
self.__preset_mode.put(mode)
|
||||
self._preset_mode.put(mode)
|
||||
except Exception as e:
|
||||
logger.error(f"Error setting preset mode to {mode}: {e}")
|
||||
|
||||
def save_file(self, filename: str | None = None, timeout_s: float = 30.0):
|
||||
if not filename:
|
||||
filename = f"KETEK_{time.strftime('%Y%m%d_%H%M%S')}.txt"
|
||||
self.__saveFile_name.put(filename)
|
||||
self._saveFile_name.put(filename)
|
||||
try:
|
||||
self.__saveFile.put(1)
|
||||
self._saveFile.put(1)
|
||||
timeout = time.time() + timeout_s
|
||||
while self.__saveFile_rbv.get() == 1:
|
||||
while self._saveFile_rbv.get() == 1:
|
||||
if time.time() > timeout:
|
||||
raise TimeoutError("Timed out waiting for save to complete")
|
||||
poll(0.1)
|
||||
@@ -127,47 +127,47 @@ class Fluorimeter(object):
|
||||
|
||||
@property
|
||||
def real_time(self):
|
||||
return self.__real_time.get()
|
||||
return self._real_time.get()
|
||||
|
||||
@real_time.setter
|
||||
def real_time(self, value):
|
||||
self.__real_time.put(value)
|
||||
self._real_time.put(value)
|
||||
|
||||
@property
|
||||
def live_time(self):
|
||||
return self.__live_time.get()
|
||||
return self._live_time.get()
|
||||
|
||||
@live_time.setter
|
||||
def live_time(self, value):
|
||||
self.__live_time.put(value)
|
||||
self._live_time.put(value)
|
||||
|
||||
def elapsed_real_time(self):
|
||||
return self.__elapsed_real_time.get()
|
||||
return self._elapsed_real_time.get()
|
||||
|
||||
def elapsed_live_time(self):
|
||||
return self.__elapsed_live_time.get()
|
||||
return self._elapsed_live_time.get()
|
||||
|
||||
def elapsed_trigger_live_time(self):
|
||||
return self.__elapsed_trigger_live_time.get()
|
||||
return self._elapsed_trigger_live_time.get()
|
||||
|
||||
def instant_dead_time(self):
|
||||
return self.__instant_dead_time.get()
|
||||
return self._instant_dead_time.get()
|
||||
|
||||
def average_dead_time(self):
|
||||
return self.__average_dead_time.get()
|
||||
return self._average_dead_time.get()
|
||||
|
||||
@property
|
||||
def slope(self):
|
||||
return self.__calibration_slope.get()
|
||||
return self._calibration_slope.get()
|
||||
|
||||
@property
|
||||
def offset(self):
|
||||
return self.__calibration_offset.get()
|
||||
return self._calibration_offset.get()
|
||||
|
||||
@property
|
||||
def poll_time(self):
|
||||
return self.__poll_time_rbv.get()
|
||||
return self._poll_time_rbv.get()
|
||||
|
||||
@poll_time.setter
|
||||
def poll_time(self, value):
|
||||
self.__poll_time.put(value)
|
||||
self._poll_time.put(value)
|
||||
|
||||
+30
-26
@@ -9,6 +9,8 @@ from aarecommon.models.beamline import MXBeamline
|
||||
from aarecommon.models.models import DAQStatusModel, FluorescenceSpectrumOutputModel
|
||||
from aarecommon.models.raster_grid import RasterGridRequest
|
||||
from aarecommon.models.rotation_scan import RotationScanRequest
|
||||
from jfjoch_client.api.default_api import DefaultApi
|
||||
from jfjoch_client.api_client import ApiClient
|
||||
|
||||
|
||||
class ScanTypeEnum(Enum):
|
||||
@@ -22,16 +24,18 @@ class ScanTypeEnum(Enum):
|
||||
|
||||
class JFJochWrapper:
|
||||
def __init__(self, bl: MXBeamline):
|
||||
self.__simulated = bl == MXBeamline.SIMULATED
|
||||
self.__url = get_jfjoch_url(bl)
|
||||
self._simulated = bl == MXBeamline.SIMULATED
|
||||
self._url = get_jfjoch_url(bl)
|
||||
|
||||
if self.__url == "simulated":
|
||||
self.__client = None
|
||||
self.__api = None
|
||||
if self._url == "simulated":
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
self._client = MagicMock(spec=ApiClient)
|
||||
self._api = MagicMock(spec=DefaultApi)
|
||||
return
|
||||
|
||||
self.__client = jfjoch_client.ApiClient(jfjoch_client.Configuration(host=self.__url))
|
||||
self.__api = jfjoch_client.DefaultApi(self.__client)
|
||||
self._client = jfjoch_client.ApiClient(jfjoch_client.Configuration(host=self._url))
|
||||
self._api = jfjoch_client.DefaultApi(self._client)
|
||||
self.cancel()
|
||||
# if not self.is_idle():
|
||||
# self.initialize()
|
||||
@@ -56,13 +60,13 @@ class JFJochWrapper:
|
||||
message,
|
||||
operation=operation,
|
||||
endpoint=endpoint,
|
||||
base_url=self.__url,
|
||||
base_url=self._url,
|
||||
status_code=self._extract_status_code(error),
|
||||
) from error
|
||||
|
||||
def initialize(self):
|
||||
try:
|
||||
self.__api.initialize_post()
|
||||
self._api.initialize_post()
|
||||
except Exception as e:
|
||||
self._raise_jfjoch_error(
|
||||
"JFJoch initialize failed", error=e, operation="POST", endpoint="initialize_post"
|
||||
@@ -70,17 +74,17 @@ class JFJochWrapper:
|
||||
|
||||
def cancel(self):
|
||||
try:
|
||||
self.__api.cancel_post()
|
||||
self._api.cancel_post()
|
||||
except Exception as e:
|
||||
self._raise_jfjoch_error(
|
||||
"JFJoch cancel failed", error=e, operation="POST", endpoint="cancel_post"
|
||||
)
|
||||
|
||||
def is_idle(self) -> bool:
|
||||
status = self.__api.status_get()
|
||||
status = self._api.status_get()
|
||||
return status.state == "Idle"
|
||||
|
||||
def __format_dataset_settings(
|
||||
def _format_dataset_settings(
|
||||
self,
|
||||
r: RasterGridRequest | RotationScanRequest,
|
||||
s: DAQStatusModel,
|
||||
@@ -192,7 +196,7 @@ class JFJochWrapper:
|
||||
|
||||
return dataset_settings
|
||||
|
||||
def __start_scan(
|
||||
def _start_scan(
|
||||
self,
|
||||
scan_type: ScanTypeEnum,
|
||||
r: RasterGridRequest | RotationScanRequest,
|
||||
@@ -200,9 +204,9 @@ class JFJochWrapper:
|
||||
f: FluorescenceSpectrumOutputModel | None = None,
|
||||
async_start: bool = True,
|
||||
):
|
||||
dataset_settings = self.__format_dataset_settings(r, s, f, async_start=async_start)
|
||||
dataset_settings = self._format_dataset_settings(r, s, f, async_start=async_start)
|
||||
try:
|
||||
self.__api.start_post(dataset_settings=dataset_settings)
|
||||
self._api.start_post(dataset_settings=dataset_settings)
|
||||
except Exception as e:
|
||||
self._raise_jfjoch_error(
|
||||
f"JFJoch data collection failed to initialize for {scan_type.value} scan with exception: {e}",
|
||||
@@ -219,18 +223,18 @@ class JFJochWrapper:
|
||||
async_start: bool = True,
|
||||
) -> None:
|
||||
if r.screening:
|
||||
self.__start_scan(ScanTypeEnum.SCREENING, r, s, f, async_start=async_start)
|
||||
self._start_scan(ScanTypeEnum.SCREENING, r, s, f, async_start=async_start)
|
||||
else:
|
||||
self.__start_scan(ScanTypeEnum.ROTATION, r, s, f, async_start=async_start)
|
||||
self._start_scan(ScanTypeEnum.ROTATION, r, s, f, async_start=async_start)
|
||||
|
||||
def measure_raster(self, r: RasterGridRequest, s: DAQStatusModel, async_start: bool = True):
|
||||
self.__start_scan(ScanTypeEnum.RASTER, r, s, async_start=async_start)
|
||||
self._start_scan(ScanTypeEnum.RASTER, r, s, async_start=async_start)
|
||||
|
||||
def wait_till_running(self, timeout: int | float = 60):
|
||||
if self.__simulated:
|
||||
if self._simulated:
|
||||
return None
|
||||
try:
|
||||
self.__api.wait_until_running_post_with_http_info(timeout=math.ceil(timeout))
|
||||
self._api.wait_until_running_post_with_http_info(timeout=math.ceil(timeout))
|
||||
return True
|
||||
except Exception as e:
|
||||
self._raise_jfjoch_error(
|
||||
@@ -241,11 +245,11 @@ class JFJochWrapper:
|
||||
)
|
||||
|
||||
def wait_till_done(self, timeout: int | float) -> jfjoch_client.models.ScanResult | None:
|
||||
if self.__simulated:
|
||||
if self._simulated:
|
||||
return None
|
||||
try:
|
||||
self.__api.wait_till_done_post_with_http_info(timeout=math.ceil(timeout))
|
||||
return self.__api.result_scan_get()
|
||||
self._api.wait_till_done_post_with_http_info(timeout=math.ceil(timeout))
|
||||
return self._api.result_scan_get()
|
||||
except Exception as e:
|
||||
self._raise_jfjoch_error(
|
||||
f"JFJoch wait till done retrieval returned an exception: {e}",
|
||||
@@ -256,7 +260,7 @@ class JFJochWrapper:
|
||||
|
||||
def detector(self) -> jfjoch_client.models.DetectorListElement:
|
||||
try:
|
||||
detector_list = self.__api.config_select_detector_get()
|
||||
detector_list = self._api.config_select_detector_get()
|
||||
except Exception as e:
|
||||
self._raise_jfjoch_error(
|
||||
f"JFJoch detector configuration retrieval failed: {e}",
|
||||
@@ -270,7 +274,7 @@ class JFJochWrapper:
|
||||
"JFJoch returned no configured detectors",
|
||||
operation="GET",
|
||||
endpoint="config_select_detector_get",
|
||||
base_url=self.__url,
|
||||
base_url=self._url,
|
||||
status_code=None,
|
||||
)
|
||||
|
||||
@@ -301,7 +305,7 @@ class JFJochWrapper:
|
||||
last_error = None
|
||||
for _ in range(3):
|
||||
try:
|
||||
return self.__api.image_buffer_image_jpeg_get(
|
||||
return self._api.image_buffer_image_jpeg_get(
|
||||
id=image_id,
|
||||
show_spots=show_spots,
|
||||
show_res_est=show_res_est,
|
||||
|
||||
@@ -24,11 +24,11 @@ class PssState:
|
||||
self._simulated = beamline == MXBeamline.SIMULATED
|
||||
BEAMLINE = beamline.value.upper()
|
||||
if self._simulated:
|
||||
self.__prohibited = None
|
||||
self.__alarm = None
|
||||
self._prohibited = None
|
||||
self._alarm = None
|
||||
else:
|
||||
self.__prohibited = PV(f"{BEAMLINE}-EH1-PSYS:PROHIBITED-STATE")
|
||||
self.__alarm = PV(f"{BEAMLINE}-EH1-PSYS:ALARM-STATE")
|
||||
self._prohibited = PV(f"{BEAMLINE}-EH1-PSYS:PROHIBITED-STATE")
|
||||
self._alarm = PV(f"{BEAMLINE}-EH1-PSYS:ALARM-STATE")
|
||||
|
||||
def is_prohibited(self) -> bool:
|
||||
"""True when the hutch is interlocked so the robot is allowed to move.
|
||||
@@ -39,7 +39,7 @@ class PssState:
|
||||
"""
|
||||
if self._simulated:
|
||||
return True
|
||||
value = self.__prohibited.get()
|
||||
value = self._prohibited.get()
|
||||
if value is None:
|
||||
logger.warning(
|
||||
"PROHIBITED-STATE PV returned no value; treating hutch as not prohibited"
|
||||
@@ -55,7 +55,7 @@ class PssState:
|
||||
"""True when ``ALARM-STATE`` is non-zero (a safety alarm is active)."""
|
||||
if self._simulated:
|
||||
return False
|
||||
value = self.__alarm.get()
|
||||
value = self._alarm.get()
|
||||
if value is None:
|
||||
return False
|
||||
if isinstance(value, str):
|
||||
|
||||
+29
-29
@@ -21,31 +21,31 @@ class Smargon(object):
|
||||
|
||||
def __init__(self, bl: MXBeamline):
|
||||
if bl == MXBeamline.X06DA:
|
||||
self.__simulated = False
|
||||
self.__base = cfg_get("daq.hardware.smargon_url", "http://x06da-smargopolo.psi.ch:3000")
|
||||
self._simulated = False
|
||||
self._base = cfg_get("daq.hardware.smargon_url", "http://x06da-smargopolo.psi.ch:3000")
|
||||
elif bl == MXBeamline.X10SA:
|
||||
self.__simulated = False
|
||||
self.__base = cfg_get("daq.hardware.smargon_url", "http://x10sa-smargopolo.psi.ch:3000")
|
||||
self._simulated = False
|
||||
self._base = cfg_get("daq.hardware.smargon_url", "http://x10sa-smargopolo.psi.ch:3000")
|
||||
elif bl == MXBeamline.X06SA:
|
||||
raise NotImplementedError("Not implemented smargon url for X06SA")
|
||||
elif bl == MXBeamline.SIMULATED:
|
||||
self.__simulated = True
|
||||
self._simulated = True
|
||||
else:
|
||||
raise Exception("unknown beamline")
|
||||
self.__pos = self.SMARGON_HOME
|
||||
self.__pos_aero = self.AERO_HOME
|
||||
self._pos = self.SMARGON_HOME
|
||||
self._pos_aero = self.AERO_HOME
|
||||
|
||||
def gonget(self, thing: str) -> dict:
|
||||
"""issue a GET for some API component on the smargopolo server
|
||||
short hand for goniometer get"""
|
||||
cmd = f"{self.__base}/{thing}"
|
||||
cmd = f"{self._base}/{thing}"
|
||||
try:
|
||||
r = requests.get(cmd, timeout=2.0)
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise SmargonCommunicationError(
|
||||
f"Smargon GET failed for '{thing}'",
|
||||
endpoint=thing,
|
||||
base_url=self.__base,
|
||||
base_url=self._base,
|
||||
operation="GET",
|
||||
) from e
|
||||
|
||||
@@ -53,7 +53,7 @@ class Smargon(object):
|
||||
raise SmargonCommunicationError(
|
||||
f"Smargon GET returned HTTP {r.status_code} for '{thing}': {r.reason}",
|
||||
endpoint=thing,
|
||||
base_url=self.__base,
|
||||
base_url=self._base,
|
||||
operation="GET",
|
||||
status_code=r.status_code,
|
||||
)
|
||||
@@ -64,7 +64,7 @@ class Smargon(object):
|
||||
raise SmargonCommunicationError(
|
||||
f"Smargon GET returned invalid JSON for '{thing}'",
|
||||
endpoint=thing,
|
||||
base_url=self.__base,
|
||||
base_url=self._base,
|
||||
operation="GET",
|
||||
status_code=r.status_code,
|
||||
) from e
|
||||
@@ -72,14 +72,14 @@ class Smargon(object):
|
||||
def gonput(self, thing: str):
|
||||
"""issue a PUT command for some API component on the smargopolo server
|
||||
short hand for goniometer put"""
|
||||
cmd = f"{self.__base}/{thing}"
|
||||
cmd = f"{self._base}/{thing}"
|
||||
try:
|
||||
r = requests.put(cmd, timeout=2.0)
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise SmargonCommunicationError(
|
||||
f"Smargon PUT failed for '{thing}'",
|
||||
endpoint=thing,
|
||||
base_url=self.__base,
|
||||
base_url=self._base,
|
||||
operation="PUT",
|
||||
) from e
|
||||
|
||||
@@ -87,7 +87,7 @@ class Smargon(object):
|
||||
raise SmargonCommunicationError(
|
||||
f"Smargon PUT returned HTTP {r.status_code} for '{thing}': {r.reason}",
|
||||
endpoint=thing,
|
||||
base_url=self.__base,
|
||||
base_url=self._base,
|
||||
operation="PUT",
|
||||
status_code=r.status_code,
|
||||
)
|
||||
@@ -104,7 +104,7 @@ class Smargon(object):
|
||||
|
||||
@mode.setter
|
||||
def mode(self, mode: SmargonMode):
|
||||
if self.__simulated:
|
||||
if self._simulated:
|
||||
return
|
||||
self.gonput(f"mode?mode={mode}")
|
||||
|
||||
@@ -122,21 +122,21 @@ class Smargon(object):
|
||||
return True
|
||||
|
||||
def enable_correction(self):
|
||||
if self.__simulated:
|
||||
if self._simulated:
|
||||
return
|
||||
|
||||
self.gonput("corr_type?corr_type=1")
|
||||
|
||||
def disable_correction(self):
|
||||
if self.__simulated:
|
||||
if self._simulated:
|
||||
return
|
||||
|
||||
self.gonput("corr_type?corr_type=0")
|
||||
|
||||
@property
|
||||
def readback(self) -> SmargonCoordinate:
|
||||
if self.__simulated:
|
||||
return self.__pos
|
||||
if self._simulated:
|
||||
return self._pos
|
||||
|
||||
scs = self.gonget("readbackSCS")
|
||||
return SmargonCoordinate(
|
||||
@@ -147,8 +147,8 @@ class Smargon(object):
|
||||
|
||||
@property
|
||||
def readback_aerotech(self) -> AerotechCoordinate:
|
||||
if self.__simulated:
|
||||
return self.__pos_aero
|
||||
if self._simulated:
|
||||
return self._pos_aero
|
||||
|
||||
acs = self.gonget("readbackAEROTECH")
|
||||
return AerotechCoordinate(
|
||||
@@ -157,8 +157,8 @@ class Smargon(object):
|
||||
|
||||
@property
|
||||
def target(self) -> SmargonCoordinate:
|
||||
if self.__simulated:
|
||||
return self.__pos
|
||||
if self._simulated:
|
||||
return self._pos
|
||||
|
||||
scs = self.gonget("targetSCS") # targetAEROTECH, #targetOMEGA
|
||||
return SmargonCoordinate(
|
||||
@@ -169,8 +169,8 @@ class Smargon(object):
|
||||
|
||||
@target.setter
|
||||
def target(self, coord: SmargonCoordinate):
|
||||
if self.__simulated:
|
||||
self.__pos = coord
|
||||
if self._simulated:
|
||||
self._pos = coord
|
||||
return
|
||||
|
||||
target_string = ""
|
||||
@@ -187,8 +187,8 @@ class Smargon(object):
|
||||
|
||||
@property
|
||||
def target_aerotech(self) -> AerotechCoordinate:
|
||||
if self.__simulated:
|
||||
return self.__pos_aero
|
||||
if self._simulated:
|
||||
return self._pos_aero
|
||||
|
||||
acs = self.gonget("targetAEROTECH") # targetAEROTECH, #targetOMEGA
|
||||
return AerotechCoordinate(
|
||||
@@ -197,8 +197,8 @@ class Smargon(object):
|
||||
|
||||
@target_aerotech.setter
|
||||
def target_aerotech(self, coord: AerotechCoordinate):
|
||||
if self.__simulated:
|
||||
self.__pos_aero = coord
|
||||
if self._simulated:
|
||||
self._pos_aero = coord
|
||||
return
|
||||
|
||||
target_string = ""
|
||||
|
||||
@@ -57,7 +57,7 @@ class TellClient:
|
||||
"""High-level Tell robot API using a pluggable backend"""
|
||||
|
||||
def __init__(self, bl: MXBeamline, backend: TellBackend | None = None):
|
||||
self.__beamline = bl
|
||||
self._beamline = bl
|
||||
self.backend = backend or PShellTellBackend(bl)
|
||||
|
||||
self._aborted = False
|
||||
|
||||
@@ -34,51 +34,51 @@ class ZMQCameraClient:
|
||||
timeout_ms: Receive timeout in milliseconds
|
||||
"""
|
||||
if beamline == MXBeamline.X06DA:
|
||||
self.__simulated = False
|
||||
self.__zmq_url = "tcp://x06da-pserv-01:9089"
|
||||
self._simulated = False
|
||||
self._zmq_url = "tcp://x06da-pserv-01:9089"
|
||||
elif beamline == MXBeamline.X10SA:
|
||||
self.__simulated = False
|
||||
self.__zmq_url = "tcp://x10sa-spark-01:9091"
|
||||
self._simulated = False
|
||||
self._zmq_url = "tcp://x10sa-spark-01:9091"
|
||||
elif beamline == MXBeamline.SIMULATED:
|
||||
self.__simulated = True
|
||||
self.__zmq_url = None
|
||||
self._simulated = True
|
||||
self._zmq_url = None
|
||||
else:
|
||||
raise Exception("unknown beamline")
|
||||
|
||||
self.__timeout_ms = timeout_ms
|
||||
self.__context: Optional[zmq.Context] = None
|
||||
self.__socket: Optional[zmq.Socket] = None
|
||||
self.__last_image: Optional[np.ndarray] = None
|
||||
self.__last_fetch_time: float = 0.0
|
||||
self.__connected = False
|
||||
self._timeout_ms = timeout_ms
|
||||
self._context: Optional[zmq.Context] = None
|
||||
self._socket: Optional[zmq.Socket] = None
|
||||
self._last_image: Optional[np.ndarray] = None
|
||||
self._last_fetch_time: float = 0.0
|
||||
self._connected = False
|
||||
|
||||
def _ensure_connected(self) -> bool:
|
||||
"""Ensure the ZMQ socket is connected. Returns True if successful."""
|
||||
if self.__connected and self.__socket is not None:
|
||||
if self._connected and self._socket is not None:
|
||||
return True
|
||||
|
||||
if self.__simulated or self.__zmq_url is None:
|
||||
if self._simulated or self._zmq_url is None:
|
||||
return False
|
||||
|
||||
try:
|
||||
if self.__context is None:
|
||||
self.__context = zmq.Context()
|
||||
if self._context is None:
|
||||
self._context = zmq.Context()
|
||||
|
||||
if self.__socket is not None:
|
||||
self.__socket.close()
|
||||
if self._socket is not None:
|
||||
self._socket.close()
|
||||
|
||||
self.__socket = self.__context.socket(zmq.SUB)
|
||||
self.__socket.setsockopt(zmq.SUBSCRIBE, b"")
|
||||
self.__socket.setsockopt(zmq.RCVTIMEO, self.__timeout_ms)
|
||||
self._socket = self._context.socket(zmq.SUB)
|
||||
self._socket.setsockopt(zmq.SUBSCRIBE, b"")
|
||||
self._socket.setsockopt(zmq.RCVTIMEO, self._timeout_ms)
|
||||
# Discard old messages, keep only the latest
|
||||
self.__socket.setsockopt(zmq.CONFLATE, 1)
|
||||
self.__socket.connect(self.__zmq_url)
|
||||
self.__connected = True
|
||||
logger.debug(f"ZMQ camera connected to {self.__zmq_url}")
|
||||
self._socket.setsockopt(zmq.CONFLATE, 1)
|
||||
self._socket.connect(self._zmq_url)
|
||||
self._connected = True
|
||||
logger.debug(f"ZMQ camera connected to {self._zmq_url}")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to connect ZMQ camera to {self.__zmq_url}: {e}")
|
||||
self.__connected = False
|
||||
logger.warning(f"Failed to connect ZMQ camera to {self._zmq_url}: {e}")
|
||||
self._connected = False
|
||||
return False
|
||||
|
||||
def get_image(self, gray: bool = False) -> Optional[np.ndarray]:
|
||||
@@ -91,18 +91,18 @@ class ZMQCameraClient:
|
||||
Returns:
|
||||
numpy array with the image, or None if unavailable.
|
||||
"""
|
||||
if self.__simulated:
|
||||
if self._simulated:
|
||||
return None
|
||||
|
||||
if not self._ensure_connected():
|
||||
return None
|
||||
|
||||
try:
|
||||
r = self.__socket.recv_multipart()
|
||||
r = self._socket.recv_multipart()
|
||||
|
||||
if len(r) < 2:
|
||||
logger.debug("ZMQ camera: incomplete message received")
|
||||
return self.__last_image
|
||||
return self._last_image
|
||||
|
||||
data = r[-1]
|
||||
header = None
|
||||
@@ -125,60 +125,60 @@ class ZMQCameraClient:
|
||||
|
||||
if gray:
|
||||
gray_img = cv2.cvtColor(rgb, cv2.COLOR_RGB2GRAY)
|
||||
self.__last_image = gray_img
|
||||
self._last_image = gray_img
|
||||
return gray_img
|
||||
else:
|
||||
self.__last_image = rgb
|
||||
self._last_image = rgb
|
||||
return rgb
|
||||
else:
|
||||
logger.debug("ZMQ camera: unexpected image format")
|
||||
return self.__last_image
|
||||
return self._last_image
|
||||
|
||||
except zmq.Again:
|
||||
logger.debug("ZMQ camera: timeout waiting for frame")
|
||||
return self.__last_image
|
||||
return self._last_image
|
||||
except Exception as e:
|
||||
logger.warning(f"ZMQ camera error: {e}")
|
||||
self.__connected = False
|
||||
return self.__last_image
|
||||
self._connected = False
|
||||
return self._last_image
|
||||
|
||||
def is_available(self) -> bool:
|
||||
"""Check if ZMQ camera is available and responding."""
|
||||
if self.__simulated:
|
||||
if self._simulated:
|
||||
return False
|
||||
|
||||
if not self._ensure_connected():
|
||||
return False
|
||||
|
||||
try:
|
||||
old_timeout = self.__socket.getsockopt(zmq.RCVTIMEO)
|
||||
self.__socket.setsockopt(zmq.RCVTIMEO, 500)
|
||||
old_timeout = self._socket.getsockopt(zmq.RCVTIMEO)
|
||||
self._socket.setsockopt(zmq.RCVTIMEO, 500)
|
||||
try:
|
||||
self.__socket.recv_multipart()
|
||||
self.__socket.setsockopt(zmq.RCVTIMEO, old_timeout)
|
||||
self._socket.recv_multipart()
|
||||
self._socket.setsockopt(zmq.RCVTIMEO, old_timeout)
|
||||
return True
|
||||
except zmq.Again:
|
||||
self.__socket.setsockopt(zmq.RCVTIMEO, old_timeout)
|
||||
self._socket.setsockopt(zmq.RCVTIMEO, old_timeout)
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def close(self):
|
||||
"""Close the ZMQ connection."""
|
||||
if self.__socket:
|
||||
if self._socket:
|
||||
try:
|
||||
self.__socket.close()
|
||||
self._socket.close()
|
||||
except Exception:
|
||||
pass
|
||||
self.__socket = None
|
||||
if self.__context:
|
||||
self._socket = None
|
||||
if self._context:
|
||||
try:
|
||||
self.__context.term()
|
||||
self._context.term()
|
||||
except Exception:
|
||||
pass
|
||||
self.__context = None
|
||||
self.__connected = False
|
||||
self._context = None
|
||||
self._connected = False
|
||||
|
||||
@property
|
||||
def url(self) -> str:
|
||||
return self.__zmq_url if self.__zmq_url else "simulated"
|
||||
return self._zmq_url if self._zmq_url else "simulated"
|
||||
|
||||
+36
-36
@@ -111,11 +111,11 @@ class MainWindow(QMainWindow):
|
||||
self._use_legacy_theme_action = None
|
||||
self._use_portrait_theme_action = None
|
||||
|
||||
self.__base_url = base_url
|
||||
self.__token = token
|
||||
self.__mounting = False
|
||||
self.__samcam_feed_banner_active = False
|
||||
self.__samcam_feed_banner_message = "Sample camera feed unavailable"
|
||||
self._base_url = base_url
|
||||
self._token = token
|
||||
self._mounting = False
|
||||
self._samcam_feed_banner_active = False
|
||||
self._samcam_feed_banner_message = "Sample camera feed unavailable"
|
||||
|
||||
self._automation_critical_banner_active = False
|
||||
self._dev_help_dialog = None
|
||||
@@ -168,7 +168,7 @@ class MainWindow(QMainWindow):
|
||||
)
|
||||
|
||||
payload = jwt.decode(token_str, options={"verify_signature": False})
|
||||
self.__decoded_token = TokenData(**payload)
|
||||
self._decoded_token = TokenData(**payload)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to decode authentication token: {e}", exc_info=True)
|
||||
QMessageBox.critical(
|
||||
@@ -242,7 +242,7 @@ class MainWindow(QMainWindow):
|
||||
|
||||
self.beamline_state_panel = BeamlineStatePanel(parent=self.left_column)
|
||||
self._beamline_state_panel_enabled = bool(
|
||||
self.__decoded_token.staff or self._show_beamline_state_panel_for_users
|
||||
self._decoded_token.staff or self._show_beamline_state_panel_for_users
|
||||
)
|
||||
|
||||
self.left_column_layout.addWidget(self.data_collection)
|
||||
@@ -349,7 +349,7 @@ class MainWindow(QMainWindow):
|
||||
self.beamline_controls_scroll = NoWheelScrollArea(top_widget)
|
||||
|
||||
self.beamline = BeamlineControls(
|
||||
self.beamline_controls_scroll, staff=self.__decoded_token.staff
|
||||
self.beamline_controls_scroll, staff=self._decoded_token.staff
|
||||
)
|
||||
top_widget_layout.addWidget(self.beamline_controls_scroll)
|
||||
self.beamline_controls_scroll.setWidget(self.beamline)
|
||||
@@ -360,7 +360,7 @@ class MainWindow(QMainWindow):
|
||||
|
||||
self.tell_samples = TellSamplePanel(samples=SampleShortInfoList(s=[]))
|
||||
self.ref_tools_panel = ReferenceToolsPanel(samples=SampleShortInfoList(s=[]))
|
||||
self.job_list_panel = SampleQueuePanel(show_user=self.__decoded_token.staff)
|
||||
self.job_list_panel = SampleQueuePanel(show_user=self._decoded_token.staff)
|
||||
|
||||
self.compact_automation_panel.play_pause_clicked.connect(self.job_list_panel.run)
|
||||
self.compact_automation_panel.skip_clicked.connect(self.job_list_panel.skip_current_sample)
|
||||
@@ -384,7 +384,7 @@ class MainWindow(QMainWindow):
|
||||
self.ref_tools_dock.setAllowedAreas(Qt.DockWidgetArea.BottomDockWidgetArea)
|
||||
self.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, self.ref_tools_dock)
|
||||
self.tabifyDockWidget(self.ref_tools_dock, self.tell_samples_dock)
|
||||
if self.__decoded_token.staff:
|
||||
if self._decoded_token.staff:
|
||||
self.ref_tools_dock.show()
|
||||
else:
|
||||
self.ref_tools_dock.hide()
|
||||
@@ -549,10 +549,10 @@ class MainWindow(QMainWindow):
|
||||
event_bus=self._tutorial_event_bus,
|
||||
)
|
||||
|
||||
self.status_bar = StatusBar(self.__decoded_token, parent=self)
|
||||
self.status_bar = StatusBar(self._decoded_token, parent=self)
|
||||
self.setStatusBar(self.status_bar)
|
||||
|
||||
self.daq = DAQWorker(base_url=self.__base_url, token=self.__token)
|
||||
self.daq = DAQWorker(base_url=self._base_url, token=self._token)
|
||||
|
||||
self.installEventFilter(self)
|
||||
|
||||
@@ -594,7 +594,7 @@ class MainWindow(QMainWindow):
|
||||
self.status_bar.baton_request_refused.connect(self._refuse_baton_request)
|
||||
|
||||
self.daq.spreadsheet.connect(self.tell_samples.new_sample_list)
|
||||
if self.__decoded_token.staff:
|
||||
if self._decoded_token.staff:
|
||||
self.daq.reference_tools.connect(self.ref_tools_panel.new_list)
|
||||
|
||||
self.beamline.samcam.changed.connect(self.daq.samcam_settings)
|
||||
@@ -620,7 +620,7 @@ class MainWindow(QMainWindow):
|
||||
self.beamline.illumination_panel.front_light.connect(self.daq.front_light)
|
||||
self.beamline.illumination_panel.back_light.connect(self.daq.back_light)
|
||||
|
||||
if self.__decoded_token.staff:
|
||||
if self._decoded_token.staff:
|
||||
self.beamline.monochromator_panel.mono_pitch_scan.connect(self.daq.mono_pitch_scan)
|
||||
self.beamline.monochromator_panel.change_energy.connect(self.daq.change_energy)
|
||||
self.beamline.abr_tweak.abr_tweak.connect(self.daq.abr_tweak)
|
||||
@@ -806,7 +806,7 @@ class MainWindow(QMainWindow):
|
||||
if self.prediction_thread is not None:
|
||||
self.daq.update.connect(self.prediction_thread.update_daq_status)
|
||||
|
||||
if self.__decoded_token.staff:
|
||||
if self._decoded_token.staff:
|
||||
self.daq.update.connect(self.beamline.monochromator_panel.update_daq_status)
|
||||
self.daq.update.connect(self.beamline.beam_size.update_daq_status)
|
||||
self.daq.update.connect(self.beamline.beam_center.update_daq_status)
|
||||
@@ -990,17 +990,17 @@ class MainWindow(QMainWindow):
|
||||
self._show_samcam_feed_banner(message or "Sample camera feed unavailable")
|
||||
|
||||
def _show_samcam_feed_banner(self, message: str) -> None:
|
||||
self.__samcam_feed_banner_message = message
|
||||
self._samcam_feed_banner_message = message
|
||||
self._show_runtime_notification(
|
||||
title="Sample camera", message=message, level="warning", sticky=True
|
||||
)
|
||||
self.__samcam_feed_banner_active = True
|
||||
self._samcam_feed_banner_active = True
|
||||
|
||||
def _clear_samcam_feed_banner(self) -> None:
|
||||
if not self.__samcam_feed_banner_active:
|
||||
if not self._samcam_feed_banner_active:
|
||||
return
|
||||
self._clear_runtime_notification()
|
||||
self.__samcam_feed_banner_active = False
|
||||
self._samcam_feed_banner_active = False
|
||||
|
||||
def _stop_axis_camera_threads(self) -> None:
|
||||
for attr_name in ("beamline_camera_thread", "gonio_camera_thread"):
|
||||
@@ -1093,7 +1093,7 @@ class MainWindow(QMainWindow):
|
||||
self.prediction_metrics_dock.setVisible(False)
|
||||
self.log_dock.setVisible(False)
|
||||
|
||||
if self.__decoded_token.staff:
|
||||
if self._decoded_token.staff:
|
||||
self.ref_tools_dock.setVisible(False)
|
||||
|
||||
self.collection_controls_scroll.setVisible(False)
|
||||
@@ -1113,7 +1113,7 @@ class MainWindow(QMainWindow):
|
||||
self.collection_controls_scroll.setVisible(self._pre_automation_left_column_visible)
|
||||
self.beamline_controls_scroll.setVisible(self._pre_automation_right_column_visible)
|
||||
|
||||
if self.__decoded_token.staff:
|
||||
if self._decoded_token.staff:
|
||||
self.ref_tools_dock.setVisible(self._pre_automation_ref_tools_visible)
|
||||
|
||||
self._in_compact_automation_view = False
|
||||
@@ -1141,8 +1141,8 @@ class MainWindow(QMainWindow):
|
||||
# ── Camera: scale-to-fit + hide legend ─────────────────────────────
|
||||
self.portrait_sample_camera.set_show_overlay_legend(False)
|
||||
try:
|
||||
self.portrait_sample_camera._SampleCameraImageLabel__autoscale = True
|
||||
self.portrait_sample_camera._SampleCameraImageLabel__scaling()
|
||||
self.portrait_sample_camera._autoscale = True
|
||||
self.portrait_sample_camera._scaling()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -1413,7 +1413,7 @@ class MainWindow(QMainWindow):
|
||||
self.tell_samples_dock.visibilityChanged.connect(show_samples_action.setChecked)
|
||||
view_menu.addAction(show_samples_action)
|
||||
|
||||
if self.__decoded_token.staff:
|
||||
if self._decoded_token.staff:
|
||||
show_reference_tools_action = QAction("Show Reference Tools", self)
|
||||
show_reference_tools_action.setCheckable(True)
|
||||
show_reference_tools_action.setChecked(True)
|
||||
@@ -1549,7 +1549,7 @@ class MainWindow(QMainWindow):
|
||||
refresh_axis_cameras_action.triggered.connect(self.refresh_axis_cameras)
|
||||
help_menu.addAction(refresh_axis_cameras_action)
|
||||
|
||||
if self.__decoded_token.staff:
|
||||
if self._decoded_token.staff:
|
||||
local_contact_action = QAction("Local Contact", self)
|
||||
local_contact_action.triggered.connect(self.show_local_contact)
|
||||
help_menu.addAction(local_contact_action)
|
||||
@@ -1596,7 +1596,7 @@ class MainWindow(QMainWindow):
|
||||
self.prediction_metrics_dock.setVisible(False)
|
||||
self.log_dock.setVisible(False)
|
||||
|
||||
if self.__decoded_token.staff:
|
||||
if self._decoded_token.staff:
|
||||
self.ref_tools_dock.setVisible(True)
|
||||
self.ref_tools_dock.raise_()
|
||||
else:
|
||||
@@ -1619,7 +1619,7 @@ class MainWindow(QMainWindow):
|
||||
if self._dev_help_dialog is None:
|
||||
self._dev_help_dialog = DeveloperHelpDialog(
|
||||
daq=self.daq,
|
||||
is_staff=bool(getattr(self.__decoded_token, "staff", False)),
|
||||
is_staff=bool(getattr(self._decoded_token, "staff", False)),
|
||||
parent=self,
|
||||
)
|
||||
self._dev_help_dialog.refresh()
|
||||
@@ -1912,7 +1912,7 @@ class MainWindow(QMainWindow):
|
||||
|
||||
# 4. Surface recovery UI
|
||||
try:
|
||||
if bool(getattr(self.__decoded_token, "staff", False)):
|
||||
if bool(getattr(self._decoded_token, "staff", False)):
|
||||
if is_detector_failure:
|
||||
QMessageBox.critical(
|
||||
self,
|
||||
@@ -1958,7 +1958,7 @@ class MainWindow(QMainWindow):
|
||||
logger.error(f"Failed to surface recovery UI after critical failure: {e}")
|
||||
|
||||
def show_beamline_recovery(self) -> None:
|
||||
if not bool(getattr(self.__decoded_token, "staff", False)):
|
||||
if not bool(getattr(self._decoded_token, "staff", False)):
|
||||
return
|
||||
if self._beamline_recovery_dialog is None:
|
||||
self._beamline_recovery_dialog = BeamlineRecoveryDialog(daq=self.daq, parent=self)
|
||||
@@ -1967,7 +1967,7 @@ class MainWindow(QMainWindow):
|
||||
self._beamline_recovery_dialog.activateWindow()
|
||||
|
||||
def show_local_contact(self, tab_name: str = "Status") -> None:
|
||||
if not bool(getattr(self.__decoded_token, "staff", False)):
|
||||
if not bool(getattr(self._decoded_token, "staff", False)):
|
||||
return
|
||||
if self._local_contact_dialog is None:
|
||||
self._local_contact_dialog = LocalContactDialog(daq=self.daq, parent=self)
|
||||
@@ -2024,7 +2024,7 @@ class MainWindow(QMainWindow):
|
||||
|
||||
self._refresh_compact_queue_preview()
|
||||
|
||||
current_session = int(getattr(self.__decoded_token, "session", -1))
|
||||
current_session = int(getattr(self._decoded_token, "session", -1))
|
||||
for gui in getattr(s, "open_guis", []) or []:
|
||||
try:
|
||||
if int(gui.session) == current_session and bool(gui.close_requested):
|
||||
@@ -2040,11 +2040,11 @@ class MainWindow(QMainWindow):
|
||||
if self._remote_close_deadline_ts is not None:
|
||||
self._clear_remote_close_request()
|
||||
|
||||
if not self.__mounting and s.state == BeamlineStateEnum.RobotSampleExchange:
|
||||
self.__mounting = True
|
||||
if not self._mounting and s.state == BeamlineStateEnum.RobotSampleExchange:
|
||||
self._mounting = True
|
||||
self.video_tab.setCurrentWidget(self.beamline_combined_panel)
|
||||
elif self.__mounting and s.state != BeamlineStateEnum.RobotSampleExchange:
|
||||
self.__mounting = False
|
||||
elif self._mounting and s.state != BeamlineStateEnum.RobotSampleExchange:
|
||||
self._mounting = False
|
||||
self.video_tab.setCurrentWidget(self.sample_camera)
|
||||
|
||||
# ========== BATON DIALOG HANDLING ==========
|
||||
@@ -2403,7 +2403,7 @@ class MainWindow(QMainWindow):
|
||||
|
||||
self._last_interaction_report_ts = now
|
||||
try:
|
||||
self.daq.report_gui_interaction(int(self.__decoded_token.session))
|
||||
self.daq.report_gui_interaction(int(self._decoded_token.session))
|
||||
except Exception as e:
|
||||
logger.debug(f"Failed to report GUI interaction: {e}")
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ class SampleQueueSpreadsheet(QAbstractTableModel):
|
||||
super().__init__(parent)
|
||||
if samples is None:
|
||||
samples = []
|
||||
self.__running = False
|
||||
self._running = False
|
||||
self._show_user = bool(show_user)
|
||||
self.samples: list[SampleShortInfo] = samples
|
||||
self.header = (
|
||||
@@ -54,7 +54,7 @@ class SampleQueueSpreadsheet(QAbstractTableModel):
|
||||
return Qt.AlignmentFlag.AlignCenter
|
||||
elif role == Qt.ItemDataRole.BackgroundRole:
|
||||
if index.row() == 0:
|
||||
if self.__running:
|
||||
if self._running:
|
||||
return QBrush(QColor(255, 102, 0))
|
||||
else:
|
||||
return QBrush(QColor(114, 159, 207))
|
||||
@@ -113,7 +113,7 @@ class SampleQueueSpreadsheet(QAbstractTableModel):
|
||||
return True
|
||||
|
||||
def set_running(self, running: bool):
|
||||
self.__running = running
|
||||
self._running = running
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def remove_sample(self, db_id: int):
|
||||
|
||||
@@ -57,12 +57,12 @@ class UserSampleSpreadsheet(QAbstractTableModel):
|
||||
]
|
||||
self.current_sample = current_sample
|
||||
self.current_puck = current_puck
|
||||
self.__sort_col = 3
|
||||
self.__sort_order = Qt.SortOrder.AscendingOrder
|
||||
self._sort_col = 3
|
||||
self._sort_order = Qt.SortOrder.AscendingOrder
|
||||
|
||||
self.__filters: dict[int, str] = {}
|
||||
self.__filter_col: int | None = 5
|
||||
self.__filter_value: str | None = None
|
||||
self._filters: dict[int, str] = {}
|
||||
self._filter_col: int | None = 5
|
||||
self._filter_value: str | None = None
|
||||
self.current_pgroup: str | None = None
|
||||
self.show_all_pgroups: bool = False
|
||||
|
||||
@@ -77,20 +77,20 @@ class UserSampleSpreadsheet(QAbstractTableModel):
|
||||
self.endResetModel()
|
||||
|
||||
def rowCount(self, parent=None):
|
||||
return len(self.__sorted_samples)
|
||||
return len(self._sorted_samples)
|
||||
|
||||
def columnCount(self, parent=None):
|
||||
return len(self.header)
|
||||
|
||||
def data(self, index, role=None):
|
||||
if role == Qt.ItemDataRole.DisplayRole:
|
||||
return get_entry(self.__sorted_samples[index.row()], index.column())
|
||||
return get_entry(self._sorted_samples[index.row()], index.column())
|
||||
elif role == Qt.ItemDataRole.TextAlignmentRole: # Align text to center
|
||||
return Qt.AlignmentFlag.AlignCenter
|
||||
elif role == Qt.ItemDataRole.BackgroundRole:
|
||||
if self.__sorted_samples[index.row()].db_id == self.current_sample:
|
||||
if self._sorted_samples[index.row()].db_id == self.current_sample:
|
||||
return QBrush(QColor(114, 159, 207)) # darker blue
|
||||
if self.__sorted_samples[index.row()].puck_name == self.current_puck:
|
||||
if self._sorted_samples[index.row()].puck_name == self.current_puck:
|
||||
return QBrush(QColor(216, 228, 253)) # light blue
|
||||
return QBrush(QColor(255, 255, 255)) # White
|
||||
return None # For other roles, return None
|
||||
@@ -118,39 +118,39 @@ class UserSampleSpreadsheet(QAbstractTableModel):
|
||||
|
||||
def sort(self, column, order):
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
self.__sort_order = order
|
||||
self.__sort_col = column
|
||||
self._sort_order = order
|
||||
self._sort_col = column
|
||||
self._sort()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def _sort(self):
|
||||
filtered = self._apply_filter(self.samples)
|
||||
if self.__sort_col == 3:
|
||||
self.__sorted_samples = sorted(
|
||||
if self._sort_col == 3:
|
||||
self._sorted_samples = sorted(
|
||||
filtered,
|
||||
key=lambda row: row.loc_str_sort(),
|
||||
reverse=(
|
||||
self.__sort_order == Qt.SortOrder.DescendingOrder
|
||||
self._sort_order == Qt.SortOrder.DescendingOrder
|
||||
), # Reverse for descending order
|
||||
)
|
||||
else:
|
||||
# Sort the samples based on the specified column and order
|
||||
self.__sorted_samples = sorted(
|
||||
self._sorted_samples = sorted(
|
||||
filtered,
|
||||
key=lambda row: get_entry(row, self.__sort_col),
|
||||
key=lambda row: get_entry(row, self._sort_col),
|
||||
reverse=(
|
||||
self.__sort_order == Qt.SortOrder.DescendingOrder
|
||||
self._sort_order == Qt.SortOrder.DescendingOrder
|
||||
), # Reverse for descending order
|
||||
)
|
||||
|
||||
def _apply_filter(self, rows: list[SampleShortInfo]) -> list[SampleShortInfo]:
|
||||
# Default filter by User using current p-group if no explicit filter set
|
||||
filters: dict[int, str] = {
|
||||
col: v for col, v in (self.__filters or {}).items() if (v or "").strip()
|
||||
col: v for col, v in (self._filters or {}).items() if (v or "").strip()
|
||||
}
|
||||
|
||||
if self.__filter_col is not None and (self.__filter_value or "").strip():
|
||||
filters[self.__filter_col] = self.__filter_value
|
||||
if self._filter_col is not None and (self._filter_value or "").strip():
|
||||
filters[self._filter_col] = self._filter_value
|
||||
|
||||
if 5 not in filters and self.current_pgroup and not self.show_all_pgroups:
|
||||
filters[5] = self.current_pgroup
|
||||
@@ -183,13 +183,13 @@ class UserSampleSpreadsheet(QAbstractTableModel):
|
||||
sample_data = SampleShortInfoList(s=[])
|
||||
|
||||
for i in sorted(set(index.row() for index in indexes)):
|
||||
sample_data.s.append(self.__sorted_samples[i])
|
||||
sample_data.s.append(self._sorted_samples[i])
|
||||
|
||||
mime_data.setText(sample_data.model_dump_json())
|
||||
return mime_data
|
||||
|
||||
def get_id(self, row: int) -> SampleShortInfo:
|
||||
return self.__sorted_samples[row]
|
||||
return self._sorted_samples[row]
|
||||
|
||||
def set_filter(self, field: str, text: str | None):
|
||||
try:
|
||||
@@ -197,22 +197,22 @@ class UserSampleSpreadsheet(QAbstractTableModel):
|
||||
except ValueError:
|
||||
col = None
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
self.__filter_col = col if text and text.strip() and col is not None else None
|
||||
self.__filter_value = text.strip() if text else None
|
||||
self._filter_col = col if text and text.strip() and col is not None else None
|
||||
self._filter_value = text.strip() if text else None
|
||||
# mirror into multi-filter map
|
||||
if col is not None:
|
||||
if text and text.strip():
|
||||
self.__filters[col] = text.strip()
|
||||
self._filters[col] = text.strip()
|
||||
else:
|
||||
self.__filters.pop(col, None)
|
||||
self._filters.pop(col, None)
|
||||
self._sort()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def clear_filter(self):
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
self.__filter_col = None
|
||||
self.__filter_value = None
|
||||
self.__filters.clear()
|
||||
self._filter_col = None
|
||||
self._filter_value = None
|
||||
self._filters.clear()
|
||||
self._sort()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
@@ -225,9 +225,9 @@ class UserSampleSpreadsheet(QAbstractTableModel):
|
||||
def set_column_filter(self, column: int, text: str | None):
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
if text and text.strip():
|
||||
self.__filters[column] = text.strip()
|
||||
self._filters[column] = text.strip()
|
||||
else:
|
||||
self.__filters.pop(column, None)
|
||||
self._filters.pop(column, None)
|
||||
self._sort()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
@@ -243,11 +243,11 @@ class UserSampleSpreadsheet(QAbstractTableModel):
|
||||
def unique_values_for_column(self, column: int, limit: int = 200) -> list[str]:
|
||||
"""Get unique values for a column from currently filtered samples (excluding this column's filter)."""
|
||||
# Get currently filtered samples, but exclude the filter for this column
|
||||
temp_filter = self.__filters.pop(column, None)
|
||||
temp_filter = self._filters.pop(column, None)
|
||||
filtered_samples = self._apply_filter(self.samples)
|
||||
# Restore the filter
|
||||
if temp_filter is not None:
|
||||
self.__filters[column] = temp_filter
|
||||
self._filters[column] = temp_filter
|
||||
|
||||
seen: set[str] = set()
|
||||
out: list[str] = []
|
||||
@@ -283,10 +283,10 @@ class UserSampleSpreadsheet(QAbstractTableModel):
|
||||
def suggested_prefixes_for_sample_name(self, limit: int = 200) -> list[str]:
|
||||
"""Get sample name prefixes from currently filtered samples (excluding column 0 filter)."""
|
||||
# Get currently filtered samples, excluding the sample name filter
|
||||
temp_filter = self.__filters.pop(0, None)
|
||||
temp_filter = self._filters.pop(0, None)
|
||||
filtered_samples = self._apply_filter(self.samples)
|
||||
if temp_filter is not None:
|
||||
self.__filters[0] = temp_filter
|
||||
self._filters[0] = temp_filter
|
||||
|
||||
rx = re.compile(r"^([A-Za-z]+)")
|
||||
counts: dict[str, int] = {}
|
||||
@@ -307,10 +307,10 @@ class UserSampleSpreadsheet(QAbstractTableModel):
|
||||
def suggested_prefixes_for_location(self, limit: int = 200) -> tuple[list[str], list[str]]:
|
||||
"""Get location prefixes from currently filtered samples (excluding column 3 filter)."""
|
||||
# Get currently filtered samples, excluding the location filter
|
||||
temp_filter = self.__filters.pop(3, None)
|
||||
temp_filter = self._filters.pop(3, None)
|
||||
filtered_samples = self._apply_filter(self.samples)
|
||||
if temp_filter is not None:
|
||||
self.__filters[3] = temp_filter
|
||||
self._filters[3] = temp_filter
|
||||
|
||||
seg_seen: set[str] = set()
|
||||
segpos_seen: set[str] = set()
|
||||
|
||||
@@ -17,7 +17,7 @@ class AbrTweakButtons(QWidget):
|
||||
def __init__(self, step_mm, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.__step_mm = step_mm
|
||||
self._step_mm = step_mm
|
||||
grid_layout = QGridLayout(self)
|
||||
grid_layout.setColumnStretch(0, 1)
|
||||
grid_layout.setColumnStretch(1, 0)
|
||||
@@ -70,16 +70,16 @@ class AbrTweakButtons(QWidget):
|
||||
self.abr_tweak.emit(
|
||||
AerotechCoordinate(
|
||||
at_mm=Coordinate(
|
||||
x=self.__step_mm * payload["x"],
|
||||
y=self.__step_mm * payload["y"],
|
||||
z=self.__step_mm * payload["z"],
|
||||
x=self._step_mm * payload["x"],
|
||||
y=self._step_mm * payload["y"],
|
||||
z=self._step_mm * payload["z"],
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@Slot(float)
|
||||
def set_step(self, val_um: float):
|
||||
self.__step_mm = val_um / 1000.0
|
||||
self._step_mm = val_um / 1000.0
|
||||
|
||||
|
||||
class AbrTweakWidget(QWidget):
|
||||
@@ -97,15 +97,15 @@ class AbrTweakWidget(QWidget):
|
||||
|
||||
grid_layout.addWidget(TitleLabel("ABR meas. pos.", self), 0, 0, 1, 3)
|
||||
|
||||
self.__abr_buttons = AbrTweakButtons(DEFAULT_ABR_STEP_UM / 1000, parent=self)
|
||||
grid_layout.addWidget(self.__abr_buttons, 1, 0, 1, 4)
|
||||
self.__abr_buttons.abr_tweak.connect(self.abr_button_pressed)
|
||||
self._abr_buttons = AbrTweakButtons(DEFAULT_ABR_STEP_UM / 1000, parent=self)
|
||||
grid_layout.addWidget(self._abr_buttons, 1, 0, 1, 4)
|
||||
self._abr_buttons.abr_tweak.connect(self.abr_button_pressed)
|
||||
|
||||
grid_layout.addWidget(QLabel("Step", parent=self), 2, 0)
|
||||
self.__step_um = NumberLineEdit(1, 1000, DEFAULT_ABR_STEP_UM, 0, parent=self)
|
||||
grid_layout.addWidget(self.__step_um, 2, 1)
|
||||
self._step_um = NumberLineEdit(1, 1000, DEFAULT_ABR_STEP_UM, 0, parent=self)
|
||||
grid_layout.addWidget(self._step_um, 2, 1)
|
||||
grid_layout.addWidget(QLabel("μm", parent=self), 2, 2)
|
||||
self.__step_um.newValue.connect(self.__abr_buttons.set_step)
|
||||
self._step_um.newValue.connect(self._abr_buttons.set_step)
|
||||
|
||||
save_button = QPushButton("Save ABR pos.")
|
||||
grid_layout.addWidget(save_button, 3, 0, 1, 3)
|
||||
@@ -129,19 +129,19 @@ class AbrTweakWidget(QWidget):
|
||||
|
||||
@Slot(DAQStatusModel)
|
||||
def update_daq_status(self, s: DAQStatusModel):
|
||||
self.__abr_buttons.gmx_label.setText(f"{s.geom.aerotech_meas.x:.3f}")
|
||||
self._abr_buttons.gmx_label.setText(f"{s.geom.aerotech_meas.x:.3f}")
|
||||
if abs(s.geom.aerotech.x) >= 0.001:
|
||||
self.__abr_buttons.gmx_label.setStyleSheet("color: rgb(255, 0, 0);")
|
||||
self._abr_buttons.gmx_label.setStyleSheet("color: rgb(255, 0, 0);")
|
||||
else:
|
||||
self.__abr_buttons.gmx_label.setStyleSheet("color: rgb(0, 0, 0);")
|
||||
self.__abr_buttons.gmy_label.setText(f"{s.geom.aerotech_meas.y:.3f}")
|
||||
self._abr_buttons.gmx_label.setStyleSheet("color: rgb(0, 0, 0);")
|
||||
self._abr_buttons.gmy_label.setText(f"{s.geom.aerotech_meas.y:.3f}")
|
||||
if abs(s.geom.aerotech.y) >= 0.001:
|
||||
self.__abr_buttons.gmy_label.setStyleSheet("color: rgb(255, 0, 0);")
|
||||
self._abr_buttons.gmy_label.setStyleSheet("color: rgb(255, 0, 0);")
|
||||
else:
|
||||
self.__abr_buttons.gmy_label.setStyleSheet("color: rgb(0, 0, 0);")
|
||||
self._abr_buttons.gmy_label.setStyleSheet("color: rgb(0, 0, 0);")
|
||||
|
||||
self.__abr_buttons.gmz_label.setText(f"{s.geom.aerotech_meas.z:.3f}")
|
||||
self._abr_buttons.gmz_label.setText(f"{s.geom.aerotech_meas.z:.3f}")
|
||||
if abs(s.geom.aerotech.z) >= 0.001:
|
||||
self.__abr_buttons.gmz_label.setStyleSheet("color: rgb(255, 0, 0);")
|
||||
self._abr_buttons.gmz_label.setStyleSheet("color: rgb(255, 0, 0);")
|
||||
else:
|
||||
self.__abr_buttons.gmz_label.setStyleSheet("color: rgb(0, 0, 0);")
|
||||
self._abr_buttons.gmz_label.setStyleSheet("color: rgb(0, 0, 0);")
|
||||
|
||||
@@ -33,23 +33,23 @@ class DataCollectionSettings(QFrame):
|
||||
self.file_path_panel = FilePathPanel(self)
|
||||
v_layout.addWidget(self.file_path_panel)
|
||||
|
||||
self.__tab_widget = QTabWidget()
|
||||
self._tab_widget = QTabWidget()
|
||||
|
||||
self.raster = RasterDataCollectionPanel(
|
||||
parent=self, raster_mgr=raster_mgr, diffraction=diffraction
|
||||
)
|
||||
self.__tab_widget.addTab(self.raster, "Raster scan")
|
||||
self._tab_widget.addTab(self.raster, "Raster scan")
|
||||
|
||||
self.screening = RotationDataCollectionPanel(parent=self, diffraction=diffraction)
|
||||
self.__tab_widget.addTab(self.screening, "Rotation")
|
||||
self._tab_widget.addTab(self.screening, "Rotation")
|
||||
|
||||
self.simple = SimpleRotationSettingsPanel(parent=self)
|
||||
self.__tab_widget.addTab(self.simple, "Simple")
|
||||
self._tab_widget.addTab(self.simple, "Simple")
|
||||
|
||||
self.fluo = FluorescenceDataCollectionPanel(parent=self)
|
||||
self.__tab_widget.addTab(self.fluo, "XRF")
|
||||
self._tab_widget.addTab(self.fluo, "XRF")
|
||||
|
||||
v_layout.addWidget(self.__tab_widget)
|
||||
v_layout.addWidget(self._tab_widget)
|
||||
v_layout.addStretch()
|
||||
|
||||
abort_button = QPushButton("Abort measurement", parent=self)
|
||||
@@ -65,13 +65,13 @@ class DataCollectionSettings(QFrame):
|
||||
self.file_path_panel.path_updated.connect(raster_mgr.update_filename)
|
||||
self.file_path_panel.path_updated.connect(self.screening.update_filename)
|
||||
self.file_path_panel.path_updated.connect(self.simple.update_filename)
|
||||
self.__sample_id = None
|
||||
self._sample_id = None
|
||||
|
||||
self.__tab_widget.currentChanged.connect(self._on_tab_changed)
|
||||
self._tab_widget.currentChanged.connect(self._on_tab_changed)
|
||||
|
||||
@Slot()
|
||||
def switch_to_raster(self):
|
||||
self.__tab_widget.setCurrentIndex(0)
|
||||
self._tab_widget.setCurrentIndex(0)
|
||||
|
||||
@Slot()
|
||||
def cancel_button_clicked(self):
|
||||
@@ -82,9 +82,9 @@ class DataCollectionSettings(QFrame):
|
||||
self.raster.update_daq_status(s)
|
||||
self.screening.update_daq_status(s)
|
||||
self.simple.update_daq_status(s)
|
||||
if s.sample is not None and s.sample.db_id != self.__sample_id:
|
||||
self.__sample_id = s.sample.db_id
|
||||
self.__tab_widget.setCurrentIndex(0)
|
||||
if s.sample is not None and s.sample.db_id != self._sample_id:
|
||||
self._sample_id = s.sample.db_id
|
||||
self._tab_widget.setCurrentIndex(0)
|
||||
|
||||
@Slot(int)
|
||||
def _on_tab_changed(self, idx: int):
|
||||
|
||||
@@ -23,18 +23,18 @@ class FilePathPanel(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
grid_layout = QGridLayout(self)
|
||||
self.__sample_name = "sample"
|
||||
self.__sample_id = -1
|
||||
self.__dewar_pos = "None"
|
||||
self.__puck_name = "Manual"
|
||||
self.__puck_pos = 0
|
||||
self.__curr_pgroup = "p11206"
|
||||
self.__beamline = mx_beamline().value.lower()
|
||||
self._sample_name = "sample"
|
||||
self._sample_id = -1
|
||||
self._dewar_pos = "None"
|
||||
self._puck_name = "Manual"
|
||||
self._puck_pos = 0
|
||||
self._curr_pgroup = "p11206"
|
||||
self._beamline = mx_beamline().value.lower()
|
||||
|
||||
self.__filename = ""
|
||||
self.__scan_kind = "raster" # default: "rotation" | "screening" | "raster"
|
||||
self._filename = ""
|
||||
self._scan_kind = "raster" # default: "rotation" | "screening" | "raster"
|
||||
|
||||
self.__formatted_date = datetime.now().strftime("%Y%m%d")
|
||||
self._formatted_date = datetime.now().strftime("%Y%m%d")
|
||||
|
||||
grid_layout.addWidget(TitleLabel("Dataset path", self), 0, 0, 1, 2)
|
||||
|
||||
@@ -86,22 +86,22 @@ class FilePathPanel(QWidget):
|
||||
|
||||
def _expand_macros(self, base: str, rn: int) -> str:
|
||||
name = f"{base}_{rn:03d}"
|
||||
name = name.replace("{date}", self.__formatted_date)
|
||||
name = name.replace("{sample}", self.__sample_name)
|
||||
name = name.replace("{CrystalName}", self.__sample_name)
|
||||
name = name.replace("{puck}", self.__puck_name)
|
||||
name = name.replace("{position}", f"{self.__puck_pos:02d}")
|
||||
name = name.replace("{sample_id}", f"{self.__sample_id}")
|
||||
name = name.replace("{beamline}", f"{self.__beamline}")
|
||||
name = name.replace("{date}", self._formatted_date)
|
||||
name = name.replace("{sample}", self._sample_name)
|
||||
name = name.replace("{CrystalName}", self._sample_name)
|
||||
name = name.replace("{puck}", self._puck_name)
|
||||
name = name.replace("{position}", f"{self._puck_pos:02d}")
|
||||
name = name.replace("{sample_id}", f"{self._sample_id}")
|
||||
name = name.replace("{beamline}", f"{self._beamline}")
|
||||
name = name.replace(
|
||||
"{prefix}", f"{self.__puck_name}/{self.__puck_pos:02d}/{self.__sample_name}"
|
||||
"{prefix}", f"{self._puck_name}/{self._puck_pos:02d}/{self._sample_name}"
|
||||
)
|
||||
return name
|
||||
|
||||
def _effective_dataset_base(self, base_no_run: str) -> str:
|
||||
root = Path("/sls/mx/data") / self.__curr_pgroup / "raw"
|
||||
root = Path("/sls/mx/data") / self._curr_pgroup / "raw"
|
||||
p = Path(base_no_run)
|
||||
match self.__scan_kind:
|
||||
match self._scan_kind:
|
||||
case "rotation":
|
||||
p = Path("data") / p
|
||||
case "screening":
|
||||
@@ -150,16 +150,16 @@ class FilePathPanel(QWidget):
|
||||
self.run_number_edit.blockSignals(False)
|
||||
|
||||
# Store the GUI’s base (without applying scan-kind transforms) for wiring into requests later
|
||||
self.__filename = self._expand_macros(base, rn)
|
||||
self._filename = self._expand_macros(base, rn)
|
||||
|
||||
# Preview label shows the effective path (what will be written)
|
||||
effective = self._effective_dataset_base(self.__filename)
|
||||
effective = self._effective_dataset_base(self._filename)
|
||||
exists = os.path.exists(f"{effective}_master.h5") or os.path.exists(effective)
|
||||
self.file_name_label.setText(effective + "_master.h5")
|
||||
self.file_name_label.setStyleSheet(
|
||||
"color: rgb(200, 0, 0);" if exists else "color: rgb(0, 0, 0);"
|
||||
)
|
||||
self.path_updated.emit(self.__filename)
|
||||
self.path_updated.emit(self._filename)
|
||||
|
||||
@Slot()
|
||||
def increment_run_number(self):
|
||||
@@ -184,44 +184,44 @@ class FilePathPanel(QWidget):
|
||||
|
||||
@property
|
||||
def filename(self) -> str:
|
||||
return self.__filename
|
||||
return self._filename
|
||||
|
||||
@Slot(SampleShortInfo)
|
||||
def update_sample(self, sample: SampleShortInfo):
|
||||
if sample is None:
|
||||
self.__sample_id = -1
|
||||
self.__sample_name = "sample"
|
||||
self.__dewar_pos = "None"
|
||||
self.__puck_name = "Manual"
|
||||
self.__puck_pos = 99
|
||||
self.directory_edit.setText(f"{self.__formatted_date}/test")
|
||||
self._sample_id = -1
|
||||
self._sample_name = "sample"
|
||||
self._dewar_pos = "None"
|
||||
self._puck_name = "Manual"
|
||||
self._puck_pos = 99
|
||||
self.directory_edit.setText(f"{self._formatted_date}/test")
|
||||
else:
|
||||
self.__sample_id = sample.db_id
|
||||
self.__sample_name = sample.sample_name
|
||||
self.__dewar_pos = sample.loc_str()
|
||||
self.__puck_name = sample.puck_name
|
||||
self.__puck_pos = sample.pin
|
||||
self._sample_id = sample.db_id
|
||||
self._sample_name = sample.sample_name
|
||||
self._dewar_pos = sample.loc_str()
|
||||
self._puck_name = sample.puck_name
|
||||
self._puck_pos = sample.pin
|
||||
self.run_number_edit.setValue(1)
|
||||
|
||||
if sample.aaredb_params is not None and sample.aaredb_params.directory is not None:
|
||||
self.directory_edit.setText(f"{sample.aaredb_params.directory}")
|
||||
elif sample.location is not None:
|
||||
self.directory_edit.setText(
|
||||
f"{self.__formatted_date}/{self.__puck_name}/{self.__puck_pos:02d}"
|
||||
f"{self._formatted_date}/{self._puck_name}/{self._puck_pos:02d}"
|
||||
)
|
||||
else:
|
||||
self.directory_edit.setText(f"{self.__formatted_date}/manual/{self.__sample_name}")
|
||||
self.directory_edit.setText(f"{self._formatted_date}/manual/{self._sample_name}")
|
||||
self.update_filename()
|
||||
|
||||
@Slot(DAQStatusModel)
|
||||
def update_daq_status(self, status: DAQStatusModel):
|
||||
if status.session.current_pgroup:
|
||||
self.__curr_pgroup = status.session.current_pgroup
|
||||
self._curr_pgroup = status.session.current_pgroup
|
||||
|
||||
@Slot(str)
|
||||
def set_scan_kind(self, kind: str):
|
||||
# kind in {"rotation","screening","raster"}
|
||||
self.__scan_kind = kind
|
||||
self._scan_kind = kind
|
||||
self.update_filename()
|
||||
|
||||
def effective_path_for_base(self, base_with_run: str) -> str:
|
||||
|
||||
@@ -13,16 +13,16 @@ class ManualSamplePanel(QWidget):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.__sample_name = "Lyso"
|
||||
self._sample_name = "Lyso"
|
||||
|
||||
self.__pgroup = "p16371"
|
||||
self._pgroup = "p16371"
|
||||
|
||||
grid_layout = QGridLayout(self)
|
||||
|
||||
grid_layout.addWidget(TitleLabel("Manual sample", self), 0, 0, 1, 3)
|
||||
|
||||
grid_layout.addWidget(QLabel("Sample"), 1, 0)
|
||||
self._text_name = QLineEdit(self.__sample_name)
|
||||
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);")
|
||||
@@ -95,7 +95,7 @@ class ManualSamplePanel(QWidget):
|
||||
|
||||
@Slot(str)
|
||||
def _name_changed(self, s: str):
|
||||
self.__sample_name = s
|
||||
self._sample_name = s
|
||||
|
||||
@Slot()
|
||||
def _add_clicked(self):
|
||||
@@ -111,14 +111,14 @@ class ManualSamplePanel(QWidget):
|
||||
db_id=-1,
|
||||
puck_name="",
|
||||
dewar_name="",
|
||||
sample_name=self.__sample_name,
|
||||
sample_name=self._sample_name,
|
||||
run_number=1, # can't be -1. So have defaulted to 1 for now.
|
||||
pin=1,
|
||||
aaredb_params=data_processing,
|
||||
user=self.__pgroup,
|
||||
user=self._pgroup,
|
||||
)
|
||||
self.sample_manual.emit(s)
|
||||
|
||||
@Slot(DAQStatusModel)
|
||||
def update_daq_status(self, s: DAQStatusModel):
|
||||
self.__pgroup = s.session.current_pgroup
|
||||
self._pgroup = s.session.current_pgroup
|
||||
|
||||
@@ -25,7 +25,7 @@ class OmegaPanel(QWidget):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.__omega = 0
|
||||
self._omega = 0
|
||||
|
||||
grid_layout = QGridLayout(self)
|
||||
|
||||
@@ -69,4 +69,4 @@ class OmegaPanel(QWidget):
|
||||
@Slot(DAQStatusModel)
|
||||
def update_daq_status(self, s: DAQStatusModel):
|
||||
self.edit.edit.update_value(s.geom.omega_deg)
|
||||
self.__omega = s.geom.omega_deg
|
||||
self._omega = s.geom.omega_deg
|
||||
|
||||
@@ -726,7 +726,7 @@ class PortraitModePanel(QWidget):
|
||||
return
|
||||
|
||||
# Still busy, wait
|
||||
if getattr(self._job_list_panel, "_SampleQueuePanel__busy", False):
|
||||
if getattr(self._job_list_panel, "_busy", False):
|
||||
return
|
||||
|
||||
# Safe to restart
|
||||
|
||||
@@ -42,17 +42,15 @@ class RasterDataCollectionPanel(ScanSettingsPanel):
|
||||
|
||||
self._previous_sample_was_none_raster = True
|
||||
|
||||
self.__n_x = raster_mgr.active_grid.n_x
|
||||
self.__n_y = raster_mgr.active_grid.n_y
|
||||
self.__size_x = raster_mgr.active_grid.grid_size_mm.x * 1000.0
|
||||
self.__size_y = raster_mgr.active_grid.grid_size_mm.y * 1000.0
|
||||
self.__total_time = raster_mgr.active_grid.exp_time_s * self.__n_x * self.__n_y
|
||||
self._n_x = raster_mgr.active_grid.n_x
|
||||
self._n_y = raster_mgr.active_grid.n_y
|
||||
self._size_x = raster_mgr.active_grid.grid_size_mm.x * 1000.0
|
||||
self._size_y = raster_mgr.active_grid.grid_size_mm.y * 1000.0
|
||||
self._total_time = raster_mgr.active_grid.exp_time_s * self._n_x * self._n_y
|
||||
|
||||
self._layout.addWidget(QLabel("Grid element size", parent=self), 3, 0)
|
||||
|
||||
self.width_enter = DbOverrideLineEdit(
|
||||
5, 100, default=self.__size_x, decimals=0, parent=self
|
||||
)
|
||||
self.width_enter = DbOverrideLineEdit(5, 100, default=self._size_x, decimals=0, parent=self)
|
||||
self.width_enter.valueChanged.connect(self.grid_size)
|
||||
self._register_override_field(self.width_enter)
|
||||
self._layout.addWidget(self.width_enter, 3, 1)
|
||||
@@ -60,7 +58,7 @@ class RasterDataCollectionPanel(ScanSettingsPanel):
|
||||
self._layout.addWidget(QLabel(" x ", parent=self), 3, 2)
|
||||
|
||||
self.height_enter = DbOverrideLineEdit(
|
||||
5, 100, default=self.__size_y, decimals=0, parent=self
|
||||
5, 100, default=self._size_y, decimals=0, parent=self
|
||||
)
|
||||
self.height_enter.valueChanged.connect(self.grid_size)
|
||||
self._register_override_field(self.height_enter)
|
||||
@@ -138,7 +136,7 @@ class RasterDataCollectionPanel(ScanSettingsPanel):
|
||||
self._layout.addItem(horizontal_spacer, 10, 0, 1, 5)
|
||||
|
||||
self._layout.addWidget(QLabel("Measurement time", parent=self), 11, 0)
|
||||
self.total_time = QLabel(f"{self.__total_time} min 0 s")
|
||||
self.total_time = QLabel(f"{self._total_time} min 0 s")
|
||||
self.total_time.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
|
||||
self._layout.addWidget(self.total_time, 11, 1, 1, 3)
|
||||
|
||||
@@ -158,9 +156,9 @@ class RasterDataCollectionPanel(ScanSettingsPanel):
|
||||
|
||||
@Slot(float)
|
||||
def grid_size(self, _: float):
|
||||
self.__size_x = self.width_enter.value
|
||||
self.__size_y = self.height_enter.value
|
||||
self.grid_size_updated.emit(self.__size_x / 1000.0, self.__size_y / 1000.0)
|
||||
self._size_x = self.width_enter.value
|
||||
self._size_y = self.height_enter.value
|
||||
self.grid_size_updated.emit(self._size_x / 1000.0, self._size_y / 1000.0)
|
||||
self.update_grid_scan_size()
|
||||
|
||||
@Slot(float)
|
||||
@@ -170,10 +168,10 @@ class RasterDataCollectionPanel(ScanSettingsPanel):
|
||||
|
||||
@Slot(int, int, float, float)
|
||||
def grid_scan_size_change(self, n_x: int, n_y: int, size_x_mm: float, size_y_mm: float):
|
||||
self.__size_x = size_x_mm * 1000.0
|
||||
self.__size_y = size_y_mm * 1000.0
|
||||
self.__n_x = n_x
|
||||
self.__n_y = n_y
|
||||
self._size_x = size_x_mm * 1000.0
|
||||
self._size_y = size_y_mm * 1000.0
|
||||
self._n_x = n_x
|
||||
self._n_y = n_y
|
||||
self.calculate_total_time()
|
||||
self.update_grid_scan_size()
|
||||
|
||||
@@ -193,17 +191,17 @@ class RasterDataCollectionPanel(ScanSettingsPanel):
|
||||
self._previous_sample_was_none_raster = True
|
||||
elif s.sample is not None:
|
||||
self._previous_sample_was_none_raster = False
|
||||
self.__beamline_state = s.state
|
||||
self._beamline_state = s.state
|
||||
|
||||
def update_grid_scan_size(self):
|
||||
# The live grid geometry is the "database"/system value for the element
|
||||
# size; a user override persists and can be reverted via the toggle.
|
||||
self.width_enter.set_db_value(self.__size_x)
|
||||
self.height_enter.set_db_value(self.__size_y)
|
||||
self.n_x_label.setText(str(self.__n_x))
|
||||
self.size_x_label.setText(f"{self.__size_x * self.__n_x:.1f}")
|
||||
self.n_y_label.setText(str(self.__n_y))
|
||||
self.size_y_label.setText(f"{self.__size_y * self.__n_y:.1f}")
|
||||
self.width_enter.set_db_value(self._size_x)
|
||||
self.height_enter.set_db_value(self._size_y)
|
||||
self.n_x_label.setText(str(self._n_x))
|
||||
self.size_x_label.setText(f"{self._size_x * self._n_x:.1f}")
|
||||
self.n_y_label.setText(str(self._n_y))
|
||||
self.size_y_label.setText(f"{self._size_y * self._n_y:.1f}")
|
||||
self.update_total_time_label()
|
||||
try:
|
||||
self.exp_time_s(self.image_time_enter.value)
|
||||
@@ -222,26 +220,26 @@ class RasterDataCollectionPanel(ScanSettingsPanel):
|
||||
]
|
||||
|
||||
def update_total_time_label(self):
|
||||
mins = int(self.__total_time // 60)
|
||||
secs = int(round(self.__total_time % 60))
|
||||
mins = int(self._total_time // 60)
|
||||
secs = int(round(self._total_time % 60))
|
||||
if secs == 60:
|
||||
mins += 1
|
||||
secs = 0
|
||||
self.total_time.setText(f"{mins} min {secs} s")
|
||||
|
||||
def calculate_total_time(self):
|
||||
if self.__n_x <= 0 or self.__n_y <= 0 or self.image_time_enter.value < 0:
|
||||
self.__total_time = 0.0
|
||||
if self._n_x <= 0 or self._n_y <= 0 or self.image_time_enter.value < 0:
|
||||
self._total_time = 0.0
|
||||
else:
|
||||
self.__total_time = self.__n_x * self.__n_y * self.image_time_enter.value * 1.3
|
||||
self._total_time = self._n_x * self._n_y * self.image_time_enter.value * 1.3
|
||||
# 30% buffer added
|
||||
# Show minutes
|
||||
self.update_total_time_label()
|
||||
|
||||
@Slot()
|
||||
def _on_evaluate_clicked(self):
|
||||
if self.__beamline_state != BeamlineStateEnum.SampleAlignment:
|
||||
logger.error(f"Beamline state {self.__beamline_state} is not Sample Alignment")
|
||||
if self._beamline_state != BeamlineStateEnum.SampleAlignment:
|
||||
logger.error(f"Beamline state {self._beamline_state} is not Sample Alignment")
|
||||
QMessageBox.critical(None, "Error", "Beamline state is not Sample Alignment")
|
||||
return
|
||||
if self.check_before_run(scan_kind="raster"):
|
||||
@@ -249,8 +247,8 @@ class RasterDataCollectionPanel(ScanSettingsPanel):
|
||||
|
||||
@Slot()
|
||||
def _on_evaluate_auto_clicked(self):
|
||||
if self.__beamline_state != BeamlineStateEnum.SampleAlignment:
|
||||
logger.error(f"Beamline state {self.__beamline_state} is not Sample Alignment")
|
||||
if self._beamline_state != BeamlineStateEnum.SampleAlignment:
|
||||
logger.error(f"Beamline state {self._beamline_state} is not Sample Alignment")
|
||||
QMessageBox.critical(None, "Error", "Beamline state is not Sample Alignment")
|
||||
return
|
||||
if self.check_before_run(scan_kind="raster"):
|
||||
|
||||
@@ -57,16 +57,16 @@ class ReferenceToolsModel(QAbstractTableModel):
|
||||
"Rotation count",
|
||||
"Screening count",
|
||||
]
|
||||
self.__sort_col = 0
|
||||
self.__sort_order = Qt.SortOrder.AscendingOrder
|
||||
self.__sorted_samples: list[SampleShortInfo] = []
|
||||
self._sort_col = 0
|
||||
self._sort_order = Qt.SortOrder.AscendingOrder
|
||||
self._sorted_samples: list[SampleShortInfo] = []
|
||||
if self.samples:
|
||||
self._sort()
|
||||
else:
|
||||
self.__sorted_samples = []
|
||||
self._sorted_samples = []
|
||||
|
||||
def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
|
||||
return len(self.__sorted_samples)
|
||||
return len(self._sorted_samples)
|
||||
|
||||
def columnCount(self, parent: QModelIndex = QModelIndex()) -> int:
|
||||
return len(self.header)
|
||||
@@ -75,15 +75,15 @@ class ReferenceToolsModel(QAbstractTableModel):
|
||||
if not index.isValid():
|
||||
return None
|
||||
|
||||
if index.row() >= len(self.__sorted_samples):
|
||||
if index.row() >= len(self._sorted_samples):
|
||||
return None
|
||||
|
||||
if role == Qt.ItemDataRole.DisplayRole:
|
||||
return get_entry(self.__sorted_samples[index.row()], index.column())
|
||||
return get_entry(self._sorted_samples[index.row()], index.column())
|
||||
elif role == Qt.ItemDataRole.TextAlignmentRole:
|
||||
return Qt.AlignmentFlag.AlignCenter
|
||||
elif role == Qt.ItemDataRole.BackgroundRole:
|
||||
if self.__sorted_samples[index.row()].db_id == self.current_reference:
|
||||
if self._sorted_samples[index.row()].db_id == self.current_reference:
|
||||
return QBrush(QColor(114, 159, 207)) # darker blue
|
||||
return QBrush(QColor(255, 255, 255)) # white
|
||||
|
||||
@@ -103,54 +103,54 @@ class ReferenceToolsModel(QAbstractTableModel):
|
||||
if self.samples: # Only sort if we have samples
|
||||
self._sort()
|
||||
else:
|
||||
self.__sorted_samples = [] # Clear sorted samples if no data
|
||||
self._sorted_samples = [] # Clear sorted samples if no data
|
||||
finally:
|
||||
self.endResetModel()
|
||||
|
||||
def sort(self, column, order):
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
self.__sort_order = order
|
||||
self.__sort_col = column
|
||||
self._sort_order = order
|
||||
self._sort_col = column
|
||||
self._sort()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def _sort(self):
|
||||
"""Sort samples, using special handling for location column."""
|
||||
if not self.samples:
|
||||
self.__sorted_samples = []
|
||||
self._sorted_samples = []
|
||||
return
|
||||
|
||||
if self.__sort_col == 0:
|
||||
if self._sort_col == 0:
|
||||
# Special sorting for location (Position column)
|
||||
self.__sorted_samples = sorted(
|
||||
self._sorted_samples = sorted(
|
||||
self.samples,
|
||||
key=lambda row: row.loc_str_sort(),
|
||||
reverse=(self.__sort_order == Qt.SortOrder.DescendingOrder),
|
||||
reverse=(self._sort_order == Qt.SortOrder.DescendingOrder),
|
||||
)
|
||||
elif self.__sort_col == 2:
|
||||
elif self._sort_col == 2:
|
||||
# Numeric sort for Mount count; place None last on ascending, first on descending
|
||||
none_sentinel = (
|
||||
float("inf") if self.__sort_order == Qt.SortOrder.AscendingOrder else float("-inf")
|
||||
float("inf") if self._sort_order == Qt.SortOrder.AscendingOrder else float("-inf")
|
||||
)
|
||||
self.__sorted_samples = sorted(
|
||||
self._sorted_samples = sorted(
|
||||
self.samples,
|
||||
key=lambda row: (
|
||||
row.mount_count if isinstance(row.mount_count, (int, float)) else none_sentinel
|
||||
),
|
||||
reverse=(self.__sort_order == Qt.SortOrder.DescendingOrder),
|
||||
reverse=(self._sort_order == Qt.SortOrder.DescendingOrder),
|
||||
)
|
||||
else:
|
||||
# String sort with empty fallback
|
||||
self.__sorted_samples = sorted(
|
||||
self._sorted_samples = sorted(
|
||||
self.samples,
|
||||
key=lambda row: get_entry(row, self.__sort_col) or "",
|
||||
reverse=(self.__sort_order == Qt.SortOrder.DescendingOrder),
|
||||
key=lambda row: get_entry(row, self._sort_col) or "",
|
||||
reverse=(self._sort_order == Qt.SortOrder.DescendingOrder),
|
||||
)
|
||||
|
||||
def get_item(self, row: int) -> Optional[SampleShortInfo]:
|
||||
"""Get sample at the given row index."""
|
||||
if 0 <= row < len(self.__sorted_samples):
|
||||
return self.__sorted_samples[row]
|
||||
if 0 <= row < len(self._sorted_samples):
|
||||
return self._sorted_samples[row]
|
||||
return None
|
||||
|
||||
def update_current_reference(self, reference_id: int | None):
|
||||
|
||||
@@ -48,13 +48,13 @@ class RotationDataCollectionPanel(ScanSettingsPanel):
|
||||
),
|
||||
)
|
||||
|
||||
self.__beamline_state = None
|
||||
self.__curr_pgroup = "p11206"
|
||||
self._beamline_state = None
|
||||
self._curr_pgroup = "p11206"
|
||||
self._filename = ""
|
||||
|
||||
self.__omega = 0
|
||||
self.__dose_mgy = 0
|
||||
self.__total_time = 0.0
|
||||
self._omega = 0
|
||||
self._dose_mgy = 0
|
||||
self._total_time = 0.0
|
||||
|
||||
self._layout.addWidget(QLabel("Start angle", parent=self), 3, 0)
|
||||
self.start_angle = NumberLineEdit(-720, 720.0, 0.0, decimals=3, parent=self)
|
||||
@@ -139,7 +139,7 @@ class RotationDataCollectionPanel(ScanSettingsPanel):
|
||||
self._register_override_field(self.image_time_enter)
|
||||
|
||||
self._layout.addWidget(QLabel("Total measurement time", parent=self), 13, 0)
|
||||
self.total_time = QLabel(f"{self.__total_time} min 0 s")
|
||||
self.total_time = QLabel(f"{self._total_time} min 0 s")
|
||||
self.total_time.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
|
||||
self._layout.addWidget(self.total_time, 13, 1, 1, 3)
|
||||
|
||||
@@ -150,7 +150,7 @@ class RotationDataCollectionPanel(ScanSettingsPanel):
|
||||
self.calculate_measurement_time()
|
||||
|
||||
self._layout.addWidget(QLabel("Dose", parent=self), 14, 0)
|
||||
self.dose = QLabel(f"{self.__dose_mgy}")
|
||||
self.dose = QLabel(f"{self._dose_mgy}")
|
||||
self.dose.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
|
||||
self._layout.addWidget(self.dose, 14, 1, 1, 3)
|
||||
self._layout.addWidget(QLabel("MGy", parent=self), 14, 4)
|
||||
@@ -167,8 +167,8 @@ class RotationDataCollectionPanel(ScanSettingsPanel):
|
||||
|
||||
@Slot()
|
||||
def run_screening(self):
|
||||
if self.__beamline_state != BeamlineStateEnum.SampleAlignment:
|
||||
logger.error(f"Beamline state {self.__beamline_state} is not Sample Alignment")
|
||||
if self._beamline_state != BeamlineStateEnum.SampleAlignment:
|
||||
logger.error(f"Beamline state {self._beamline_state} is not Sample Alignment")
|
||||
QMessageBox.critical(None, "Error", "Beamline state is not Sample Alignment")
|
||||
return
|
||||
if not self.check_before_run(scan_kind="screening"):
|
||||
@@ -210,8 +210,8 @@ class RotationDataCollectionPanel(ScanSettingsPanel):
|
||||
|
||||
@Slot()
|
||||
def run_measurement(self):
|
||||
if self.__beamline_state != BeamlineStateEnum.SampleAlignment:
|
||||
logger.error(f"Beamline state {self.__beamline_state} is not Sample Alignment")
|
||||
if self._beamline_state != BeamlineStateEnum.SampleAlignment:
|
||||
logger.error(f"Beamline state {self._beamline_state} is not Sample Alignment")
|
||||
QMessageBox.critical(None, "Error", "Beamline state is not Sample Alignment")
|
||||
return
|
||||
if not self.check_before_run(scan_kind="rotation"):
|
||||
@@ -243,8 +243,8 @@ class RotationDataCollectionPanel(ScanSettingsPanel):
|
||||
return round(total_angle / image_angle)
|
||||
|
||||
def update_total_time_label(self):
|
||||
mins = int(self.__total_time // 60)
|
||||
secs = int(round(self.__total_time % 60))
|
||||
mins = int(self._total_time // 60)
|
||||
secs = int(round(self._total_time % 60))
|
||||
if secs == 60:
|
||||
mins += 1
|
||||
secs = 0
|
||||
@@ -256,10 +256,10 @@ class RotationDataCollectionPanel(ScanSettingsPanel):
|
||||
or self.total_angle.value <= 0
|
||||
or self.image_time_enter.value < 0
|
||||
):
|
||||
self.__total_time = 0.0
|
||||
self._total_time = 0.0
|
||||
self.update_total_time_label()
|
||||
return
|
||||
self.__total_time = (
|
||||
self._total_time = (
|
||||
self.total_angle.value / self.image_angle.value
|
||||
) * self.image_time_enter.value
|
||||
self.update_total_time_label()
|
||||
@@ -271,7 +271,7 @@ class RotationDataCollectionPanel(ScanSettingsPanel):
|
||||
@Slot(DAQStatusModel)
|
||||
def update_daq_status(self, s: DAQStatusModel):
|
||||
super().update_daq_status(s)
|
||||
self.__omega = s.geom.omega_deg
|
||||
self._omega = s.geom.omega_deg
|
||||
|
||||
can_edit = getattr(self, "_can_edit_params", False)
|
||||
for w in (
|
||||
@@ -289,20 +289,20 @@ class RotationDataCollectionPanel(ScanSettingsPanel):
|
||||
|
||||
self.calculate_measurement_time()
|
||||
if s.session.current_pgroup:
|
||||
self.__curr_pgroup = s.session.current_pgroup
|
||||
self._curr_pgroup = s.session.current_pgroup
|
||||
lambda_a = s.diffraction.wavelength_angstrom
|
||||
kdose = 2000 / (lambda_a * lambda_a)
|
||||
beam_area = s.geom.beam_size_mm.x * s.geom.beam_size_mm.y * 1e6
|
||||
time = self.image_number() * self.image_time_enter.value
|
||||
|
||||
self.__dose_mgy = (time * s.bl.flux_ph_s * self._transmission) / (beam_area * kdose)
|
||||
self.dose.setText(f"{(self.__dose_mgy / 1e6):.1f}")
|
||||
self.__beamline_state = s.state
|
||||
self._dose_mgy = (time * s.bl.flux_ph_s * self._transmission) / (beam_area * kdose)
|
||||
self.dose.setText(f"{(self._dose_mgy / 1e6):.1f}")
|
||||
self._beamline_state = s.state
|
||||
|
||||
@Slot()
|
||||
def update_omega_start(self):
|
||||
if not self.start_angle.isReadOnly():
|
||||
self.start_angle.force_update_value(self.__omega)
|
||||
self.start_angle.force_update_value(self._omega)
|
||||
|
||||
def get_parameter_mappings(self):
|
||||
"""Return rotation-specific parameter mappings."""
|
||||
|
||||
@@ -46,7 +46,7 @@ class SamcamPanel(QWidget):
|
||||
self.exposure_spinbox.setStyleSheet("QDoubleSpinBox { background-color: white; }")
|
||||
self.exposure_spinbox.setDecimals(3)
|
||||
|
||||
self.exposure_spinbox.valueChanged.connect(self.__changed)
|
||||
self.exposure_spinbox.valueChanged.connect(self._changed)
|
||||
exposure_layout.addWidget(exposure_label)
|
||||
exposure_layout.addWidget(self.exposure_spinbox)
|
||||
|
||||
@@ -58,7 +58,7 @@ class SamcamPanel(QWidget):
|
||||
self.gain_spinbox.setSingleStep(1)
|
||||
self.gain_spinbox.setDecimals(1)
|
||||
self.gain_spinbox.setStyleSheet("QDoubleSpinBox { background-color: white; }")
|
||||
self.gain_spinbox.valueChanged.connect(self.__changed)
|
||||
self.gain_spinbox.valueChanged.connect(self._changed)
|
||||
gain_layout.addWidget(gain_label)
|
||||
gain_layout.addWidget(self.gain_spinbox)
|
||||
|
||||
@@ -84,7 +84,7 @@ class SamcamPanel(QWidget):
|
||||
screenshot_message_layout.addWidget(self.screenshot_message_edit)
|
||||
|
||||
self.screenshot_button = QPushButton("Take screenshot")
|
||||
self.screenshot_button.clicked.connect(self.__request_screenshot)
|
||||
self.screenshot_button.clicked.connect(self._request_screenshot)
|
||||
|
||||
# Show detections checkbox
|
||||
detections_layout = QHBoxLayout()
|
||||
@@ -160,14 +160,14 @@ class SamcamPanel(QWidget):
|
||||
layout.addLayout(target_color_layout)
|
||||
self.setLayout(layout)
|
||||
|
||||
def __changed(self):
|
||||
def _changed(self):
|
||||
self.changed.emit(
|
||||
SampleCameraSettings(
|
||||
gain=self.gain_spinbox.value(), exposure=self.exposure_spinbox.value()
|
||||
)
|
||||
)
|
||||
|
||||
def __request_screenshot(self):
|
||||
def _request_screenshot(self):
|
||||
self.screenshot_requested.emit(
|
||||
self.screenshot_filename_edit.text(), self.screenshot_message_edit.text()
|
||||
)
|
||||
|
||||
@@ -41,12 +41,12 @@ class SampleQueuePanel(QFrame):
|
||||
self, parent=None, samples: SampleShortInfoList | None = None, show_user: bool = False
|
||||
):
|
||||
super().__init__(parent)
|
||||
self.__baton_holder = None
|
||||
self.__busy = None
|
||||
self.__beamline_state = None
|
||||
self.__pause = True
|
||||
self.__set_to_pause = False
|
||||
self.__warning_msg_box = None
|
||||
self._baton_holder = None
|
||||
self._busy = None
|
||||
self._beamline_state = None
|
||||
self._pause = True
|
||||
self._set_to_pause = False
|
||||
self._warning_msg_box = None
|
||||
self._current_db_id: int | None = None
|
||||
self._step_through = False
|
||||
self.ring_current = None
|
||||
@@ -55,10 +55,10 @@ class SampleQueuePanel(QFrame):
|
||||
self._tell_connected = True
|
||||
self.loop_restart_requested = None
|
||||
|
||||
self.__recovery_timer = QTimer(self)
|
||||
self.__recovery_timer.setSingleShot(True)
|
||||
self.__recovery_timer.setInterval(600 * 1000) # 10 minutes
|
||||
self.__recovery_timer.timeout.connect(self.resume_automation)
|
||||
self._recovery_timer = QTimer(self)
|
||||
self._recovery_timer.setSingleShot(True)
|
||||
self._recovery_timer.setInterval(600 * 1000) # 10 minutes
|
||||
self._recovery_timer.timeout.connect(self.resume_automation)
|
||||
|
||||
self.setFrameShape(QFrame.Shape.StyledPanel)
|
||||
self.setFrameShadow(QFrame.Shadow.Raised)
|
||||
@@ -125,7 +125,7 @@ class SampleQueuePanel(QFrame):
|
||||
|
||||
def _samples_left_in_queue(self) -> int:
|
||||
total = len(self.table_model.samples)
|
||||
if self.__pause or total == 0:
|
||||
if self._pause or total == 0:
|
||||
return total
|
||||
return max(0, total - 1)
|
||||
|
||||
@@ -181,7 +181,7 @@ class SampleQueuePanel(QFrame):
|
||||
self.table_model.remove_sample(current.db_id)
|
||||
self._current_db_id = None
|
||||
|
||||
if not self.__pause and len(self.table_model.samples) > 0:
|
||||
if not self._pause and len(self.table_model.samples) > 0:
|
||||
next_item = self.table_model.samples[0]
|
||||
if self._step_through:
|
||||
self.pause_automation(set_id_to_None=False)
|
||||
@@ -229,11 +229,11 @@ class SampleQueuePanel(QFrame):
|
||||
return len(self._bad_conditions()) == 0
|
||||
|
||||
def resume_automation(self):
|
||||
if self.__warning_msg_box:
|
||||
self.__warning_msg_box.done(0)
|
||||
self.__warning_msg_box = None
|
||||
if self._warning_msg_box:
|
||||
self._warning_msg_box.done(0)
|
||||
self._warning_msg_box = None
|
||||
|
||||
if self.__pause and len(self.table_model.samples) > 0:
|
||||
if self._pause and len(self.table_model.samples) > 0:
|
||||
logger.info("Resuming automation after timeout")
|
||||
self.run()
|
||||
|
||||
@@ -242,34 +242,34 @@ class SampleQueuePanel(QFrame):
|
||||
self.resume_automation()
|
||||
|
||||
def show_warning_recovery_dialog(self):
|
||||
self.__warning_msg_box = QMessageBox(self)
|
||||
self.__warning_msg_box.setIcon(QMessageBox.Icon.Warning)
|
||||
self.__warning_msg_box.setWindowTitle("TELL Warning")
|
||||
self.__warning_msg_box.setText("TELL reported a warning. Please see console for details.")
|
||||
self.__warning_msg_box.setInformativeText(
|
||||
self._warning_msg_box = QMessageBox(self)
|
||||
self._warning_msg_box.setIcon(QMessageBox.Icon.Warning)
|
||||
self._warning_msg_box.setWindowTitle("TELL Warning")
|
||||
self._warning_msg_box.setText("TELL reported a warning. Please see console for details.")
|
||||
self._warning_msg_box.setInformativeText(
|
||||
"Automation paused for 10 minutes to allow sufficient dry time."
|
||||
"\nClick 'Continue Now' to resume immediately, or wait for auto-resume."
|
||||
)
|
||||
|
||||
continue_btn = self.__warning_msg_box.addButton(
|
||||
continue_btn = self._warning_msg_box.addButton(
|
||||
"Continue Now", QMessageBox.ButtonRole.AcceptRole
|
||||
)
|
||||
self.__warning_msg_box.setWindowModality(Qt.WindowModality.NonModal)
|
||||
self._warning_msg_box.setWindowModality(Qt.WindowModality.NonModal)
|
||||
continue_btn.clicked.connect(self.manual_resume_from_warning)
|
||||
self.__warning_msg_box.show()
|
||||
self._warning_msg_box.show()
|
||||
|
||||
def show_error_dialog(self, title: str, msg: str, info: str | None = None):
|
||||
self.__warning_msg_box = QMessageBox(self)
|
||||
self.__warning_msg_box.setIcon(QMessageBox.Icon.Warning)
|
||||
self.__warning_msg_box.setWindowTitle(title)
|
||||
self.__warning_msg_box.setText(msg)
|
||||
self._warning_msg_box = QMessageBox(self)
|
||||
self._warning_msg_box.setIcon(QMessageBox.Icon.Warning)
|
||||
self._warning_msg_box.setWindowTitle(title)
|
||||
self._warning_msg_box.setText(msg)
|
||||
if info:
|
||||
self.__warning_msg_box.setInformativeText(info)
|
||||
self.__warning_msg_box.setWindowModality(Qt.WindowModality.NonModal)
|
||||
self.__warning_msg_box.show()
|
||||
self._warning_msg_box.setInformativeText(info)
|
||||
self._warning_msg_box.setWindowModality(Qt.WindowModality.NonModal)
|
||||
self._warning_msg_box.show()
|
||||
|
||||
def is_running(self):
|
||||
return not self.__pause
|
||||
return not self._pause
|
||||
|
||||
def _finish_empty_queue(self):
|
||||
self.pause_automation()
|
||||
@@ -290,14 +290,14 @@ class SampleQueuePanel(QFrame):
|
||||
self.unmount.emit()
|
||||
|
||||
def run(self):
|
||||
self.__recovery_timer.stop()
|
||||
self._recovery_timer.stop()
|
||||
|
||||
# Pausing must ALWAYS be possible, regardless of beamline state, busy
|
||||
# flag, baton or maintenance. Only starting/resuming is gated on
|
||||
# conditions, so handle the pause toggle first and return immediately.
|
||||
if not self.__pause:
|
||||
self.__set_to_pause = True
|
||||
self.__pause = True
|
||||
if not self._pause:
|
||||
self._set_to_pause = True
|
||||
self._pause = True
|
||||
self.table_model.set_running(False)
|
||||
self.play_button.setText("▶ Run")
|
||||
self._emit_samples_in_queue_changed()
|
||||
@@ -307,11 +307,11 @@ class SampleQueuePanel(QFrame):
|
||||
# --- Starting/resuming: require the beamline to be in a good state. ---
|
||||
checks_enabled = self._checks_enabled()
|
||||
if checks_enabled:
|
||||
if self.__warning_msg_box:
|
||||
self.__warning_msg_box.done(0)
|
||||
self.__warning_msg_box = None
|
||||
if self._warning_msg_box:
|
||||
self._warning_msg_box.done(0)
|
||||
self._warning_msg_box = None
|
||||
|
||||
if self.__beamline_state != BeamlineStateEnum.SampleAlignment:
|
||||
if self._beamline_state != BeamlineStateEnum.SampleAlignment:
|
||||
self.show_error_dialog(
|
||||
title="wrong beamline state",
|
||||
msg="Cannot run automation while beamline is not in sample alignment state",
|
||||
@@ -319,8 +319,8 @@ class SampleQueuePanel(QFrame):
|
||||
)
|
||||
return
|
||||
|
||||
if self.__busy:
|
||||
logger.error(f"Cannot run automation while beamline is busy. Busy flag = {self.__busy}")
|
||||
if self._busy:
|
||||
logger.error(f"Cannot run automation while beamline is busy. Busy flag = {self._busy}")
|
||||
self.show_error_dialog(
|
||||
title="Beamline is busy",
|
||||
msg="Cannot run automation while beamline is busy",
|
||||
@@ -328,7 +328,7 @@ class SampleQueuePanel(QFrame):
|
||||
)
|
||||
return
|
||||
|
||||
if self.__baton_holder is SessionsStateEnum.Vacant:
|
||||
if self._baton_holder is SessionsStateEnum.Vacant:
|
||||
self.show_error_dialog(
|
||||
title="Session is vacant",
|
||||
msg="Starting automation while session is vacant is not currently implemented",
|
||||
@@ -337,7 +337,7 @@ class SampleQueuePanel(QFrame):
|
||||
)
|
||||
return
|
||||
|
||||
elif self.__baton_holder is not SessionsStateEnum.OwnedByYou:
|
||||
elif self._baton_holder is not SessionsStateEnum.OwnedByYou:
|
||||
self.show_error_dialog(
|
||||
title="You do not hold the baton",
|
||||
msg="You do not hold the baton.",
|
||||
@@ -347,7 +347,7 @@ class SampleQueuePanel(QFrame):
|
||||
)
|
||||
return
|
||||
|
||||
if self.__beamline_state is BeamlineStateEnum.Maintenance:
|
||||
if self._beamline_state is BeamlineStateEnum.Maintenance:
|
||||
self.show_error_dialog(
|
||||
title="Maintenance mode",
|
||||
msg="Cannot run automation while beamline is in maintenance mode",
|
||||
@@ -371,8 +371,8 @@ class SampleQueuePanel(QFrame):
|
||||
return
|
||||
|
||||
self.table_model.set_running(True)
|
||||
self.__set_to_pause = False
|
||||
self.__pause = False
|
||||
self._set_to_pause = False
|
||||
self._pause = False
|
||||
self.play_button.setText("⏸ Pause")
|
||||
current = self.table_model.samples[0]
|
||||
self._current_db_id = current.db_id
|
||||
@@ -400,13 +400,13 @@ class SampleQueuePanel(QFrame):
|
||||
self._experiment_shutter_state = s.bl.exp_shutter_open
|
||||
self._door_prohibited = getattr(s.bl, "pss_prohibited", None)
|
||||
self._tell_connected = getattr(s, "tell_connected", True)
|
||||
self.__beamline_state = s.state
|
||||
self.__busy = s.busy
|
||||
self.__baton_holder = s.session.session
|
||||
self._beamline_state = s.state
|
||||
self._busy = s.busy
|
||||
self._baton_holder = s.session.session
|
||||
|
||||
def pause_automation(self, set_id_to_None: bool = True):
|
||||
self.table_model.set_running(False)
|
||||
self.__pause = True
|
||||
self._pause = True
|
||||
self.play_button.setText("▶ Run")
|
||||
if set_id_to_None:
|
||||
self._current_db_id = None
|
||||
@@ -427,7 +427,7 @@ class SampleQueuePanel(QFrame):
|
||||
if conditions_auto_check(self, msg, self._conditions_ok):
|
||||
logger.debug("Conditions recovered or user chose to continue; resuming automation")
|
||||
self.table_model.set_running(True)
|
||||
self.__pause = False
|
||||
self._pause = False
|
||||
self.play_button.setText("⏸ Pause")
|
||||
self._emit_samples_in_queue_changed()
|
||||
else:
|
||||
@@ -440,7 +440,7 @@ class SampleQueuePanel(QFrame):
|
||||
self.table_model.remove_sample(db_id)
|
||||
self._current_db_id = None
|
||||
|
||||
if not self.__pause and len(self.table_model.samples) > 0:
|
||||
if not self._pause and len(self.table_model.samples) > 0:
|
||||
next_item = self.table_model.samples[0]
|
||||
if self._step_through:
|
||||
self.pause_automation(set_id_to_None=False)
|
||||
@@ -457,7 +457,7 @@ class SampleQueuePanel(QFrame):
|
||||
self._current_db_id = None
|
||||
self.pause_automation()
|
||||
logger.warning("TELL Warning: Pausing for 10 minutes.")
|
||||
self.__recovery_timer.start()
|
||||
self._recovery_timer.start()
|
||||
self.show_warning_recovery_dialog()
|
||||
|
||||
elif reply == "Critical":
|
||||
@@ -485,7 +485,7 @@ class SampleQueuePanel(QFrame):
|
||||
else:
|
||||
self.table_model.remove_sample(db_id)
|
||||
self._current_db_id = None
|
||||
if not self.__pause and len(self.table_model.samples) > 0:
|
||||
if not self._pause and len(self.table_model.samples) > 0:
|
||||
next_item = self.table_model.samples[0]
|
||||
if self._step_through:
|
||||
self.pause_automation(set_id_to_None=False)
|
||||
|
||||
@@ -35,7 +35,7 @@ class ScanSettingsPanel(QWidget):
|
||||
parent=None,
|
||||
):
|
||||
super().__init__(parent)
|
||||
self.__diffraction = diffraction
|
||||
self._diffraction = diffraction
|
||||
|
||||
self._transmission = default_transmission
|
||||
self._dtz = default_dtz
|
||||
@@ -167,10 +167,10 @@ class ScanSettingsPanel(QWidget):
|
||||
def update_daq_status(self, s: DAQStatusModel):
|
||||
self.dtz_enter.update_limits(s.bl.dtz_min, s.bl.dtz_max)
|
||||
self.high_res_enter.update_limits(
|
||||
self.__diffraction.resolution_angstrom(s.bl.dtz_min),
|
||||
self.__diffraction.resolution_angstrom(s.bl.dtz_max),
|
||||
self._diffraction.resolution_angstrom(s.bl.dtz_min),
|
||||
self._diffraction.resolution_angstrom(s.bl.dtz_max),
|
||||
)
|
||||
self.__diffraction = s.diffraction
|
||||
self._diffraction = s.diffraction
|
||||
self._ring_current = s.bl.ring_current_mA
|
||||
self._experiment_shutter_state = s.bl.exp_shutter_open
|
||||
self._door_prohibited = getattr(s.bl, "pss_prohibited", None)
|
||||
@@ -214,10 +214,10 @@ class ScanSettingsPanel(QWidget):
|
||||
self._high_res = float(self.high_res_enter.value)
|
||||
|
||||
def _dtz_to_res(self, dtz: float) -> float:
|
||||
return self.__diffraction.resolution_angstrom(dtz)
|
||||
return self._diffraction.resolution_angstrom(dtz)
|
||||
|
||||
def _res_to_dtz(self, res: float) -> float:
|
||||
dtz = self.__diffraction.calc_dtz_mm(res)
|
||||
dtz = self._diffraction.calc_dtz_mm(res)
|
||||
return self.MIN_DTZ if dtz < self.MIN_DTZ else dtz
|
||||
|
||||
@Slot(float)
|
||||
|
||||
@@ -53,8 +53,8 @@ class SmargonPanel(QWidget):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.__geom: SampleGeometryModel | None = None
|
||||
self.__step = 100
|
||||
self._geom: SampleGeometryModel | None = None
|
||||
self._step = 100
|
||||
|
||||
grid_layout = QGridLayout(self)
|
||||
|
||||
@@ -89,7 +89,7 @@ class SmargonPanel(QWidget):
|
||||
|
||||
@Slot(float)
|
||||
def step_changed(self, f: float):
|
||||
self.__step = f
|
||||
self._step = f
|
||||
|
||||
@Slot()
|
||||
def home(self):
|
||||
@@ -108,13 +108,13 @@ class SmargonPanel(QWidget):
|
||||
def update_daq_status(self, s: DAQStatusModel):
|
||||
self.chi_enter.update_value(s.geom.smargon.chi_deg)
|
||||
self.phi_enter.update_value(s.geom.smargon.phi_deg)
|
||||
self.__geom = s.geom
|
||||
self._geom = s.geom
|
||||
|
||||
@Slot(Coordinate)
|
||||
def smargon_rel(self, c: Coordinate):
|
||||
if self.__geom is None:
|
||||
if self._geom is None:
|
||||
return
|
||||
|
||||
self.smargon.emit(
|
||||
SmargonCoordinate(sh_mm=self.__geom.beamline_to_smargon(c * self.__step / 1000.0))
|
||||
SmargonCoordinate(sh_mm=self._geom.beamline_to_smargon(c * self._step / 1000.0))
|
||||
)
|
||||
|
||||
@@ -33,9 +33,9 @@ class SimpleRotationSettingsPanel(QWidget):
|
||||
self._filename = ""
|
||||
self.dtz = 120
|
||||
self.image_time_s = 0.1
|
||||
self.__d = None
|
||||
self._d = None
|
||||
self._temperature = 100
|
||||
self.__omega = 0
|
||||
self._omega = 0
|
||||
self._wilson_b = None
|
||||
self.flux_ph_s = None
|
||||
self.total_time_s = 0
|
||||
@@ -211,12 +211,12 @@ class SimpleRotationSettingsPanel(QWidget):
|
||||
|
||||
@Slot(DAQStatusModel)
|
||||
def update_daq_status(self, s: DAQStatusModel):
|
||||
self.__d = s
|
||||
self._d = s
|
||||
# TODO only update best_res after raster finished otherwise ask to update. or have toggle to overwrite with user value
|
||||
# TODO only take best res from flat face scan
|
||||
# TODO identify flat face!!!!
|
||||
best_res = s.last_best_res
|
||||
self.__omega = s.geom.omega_deg
|
||||
self._omega = s.geom.omega_deg
|
||||
|
||||
if best_res is not None:
|
||||
# clamp to control limits and update field; this will also trigger recalculation
|
||||
@@ -274,7 +274,7 @@ class SimpleRotationSettingsPanel(QWidget):
|
||||
|
||||
@Slot()
|
||||
def update_omega_start(self):
|
||||
self.start_angle_enter.update_value(self.__omega)
|
||||
self.start_angle_enter.update_value(self._omega)
|
||||
|
||||
def update_total_time_label(self):
|
||||
mins = int(self.total_time_s // 60)
|
||||
@@ -285,7 +285,7 @@ class SimpleRotationSettingsPanel(QWidget):
|
||||
self.total_time.setText(f"{mins} min {secs} s")
|
||||
|
||||
def update_calculated_labels(self):
|
||||
if self.__d is None:
|
||||
if self._d is None:
|
||||
return
|
||||
flux = 2.5e11 # TODO link flux
|
||||
# TODO add start angle
|
||||
@@ -301,10 +301,10 @@ class SimpleRotationSettingsPanel(QWidget):
|
||||
|
||||
self.target_res_label.setText(f"{d_tar:.2f}")
|
||||
|
||||
Kdose = 2000 / (self.__d.diffraction.wavelength_angstrom**2)
|
||||
Kdose = 2000 / (self._d.diffraction.wavelength_angstrom**2)
|
||||
|
||||
beam_size_um_y = self.__d.geom.beam_size_mm.y * 1000
|
||||
beam_size_um_x = self.__d.geom.beam_size_mm.x * 1000
|
||||
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
|
||||
@@ -348,7 +348,7 @@ class SimpleRotationSettingsPanel(QWidget):
|
||||
else:
|
||||
self.transmission = 1.0
|
||||
|
||||
self.dtz = self.__d.diffraction.calc_dtz_mm(d_tar)
|
||||
self.dtz = self._d.diffraction.calc_dtz_mm(d_tar)
|
||||
if self.dtz < 108:
|
||||
self.dtz = 108
|
||||
|
||||
@@ -357,9 +357,9 @@ class SimpleRotationSettingsPanel(QWidget):
|
||||
|
||||
if self.dtz <= 0.0:
|
||||
self.dtz_label.setText("""<span style="color: red ; ">-</span>""")
|
||||
elif self.dtz < self.__d.bl.dtz_min:
|
||||
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
|
||||
self.dtz = self._d.bl.dtz_min
|
||||
else:
|
||||
self.dtz_label.setText(f"{self.dtz:.2f}")
|
||||
|
||||
|
||||
@@ -246,5 +246,5 @@ class TellSamplePanel(QFrame):
|
||||
self.curr_sample_label.setText(base_text)
|
||||
|
||||
if status.session.current_pgroup is not None:
|
||||
self.__current_pgroup = status.session.current_pgroup
|
||||
self.table_model.set_default_user_filter(self.__current_pgroup)
|
||||
self._current_pgroup = status.session.current_pgroup
|
||||
self.table_model.set_default_user_filter(self._current_pgroup)
|
||||
|
||||
@@ -12,10 +12,10 @@ class ZoomPanel(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.__zoom = 1.0
|
||||
self._zoom = 1.0
|
||||
|
||||
grid_layout = QGridLayout(self)
|
||||
self.__zoom_settings = [
|
||||
self._zoom_settings = [
|
||||
{"name": "1.0x", "value": 1},
|
||||
{"name": "2.0x", "value": 280},
|
||||
{"name": "3.5x", "value": 500},
|
||||
@@ -26,13 +26,13 @@ class ZoomPanel(QWidget):
|
||||
grid_layout.addWidget(TitleLabel("Zoom", self), 0, 0, 1, 2)
|
||||
i = 2
|
||||
|
||||
self.__buttons = []
|
||||
for z in self.__zoom_settings:
|
||||
self._buttons = []
|
||||
for z in self._zoom_settings:
|
||||
button = ButtonWithPayload(z["name"], {"zoom": z["value"]})
|
||||
grid_layout.addWidget(button, int(i / 2), int(i % 2))
|
||||
i += 1
|
||||
button.pressed.connect(self.set_zoom)
|
||||
self.__buttons.append(button)
|
||||
self._buttons.append(button)
|
||||
|
||||
@Slot(dict)
|
||||
def set_zoom(self, payload: dict):
|
||||
@@ -40,9 +40,9 @@ class ZoomPanel(QWidget):
|
||||
|
||||
@Slot(DAQStatusModel)
|
||||
def update_daq_status(self, status: DAQStatusModel):
|
||||
self.__zoom = status.bl.zoom
|
||||
for z in self.__zoom_settings:
|
||||
if z["value"] == self.__zoom:
|
||||
self.__buttons[self.__zoom_settings.index(z)].setStyleSheet("font-weight: bold;")
|
||||
self._zoom = status.bl.zoom
|
||||
for z in self._zoom_settings:
|
||||
if z["value"] == self._zoom:
|
||||
self._buttons[self._zoom_settings.index(z)].setStyleSheet("font-weight: bold;")
|
||||
else:
|
||||
self.__buttons[self.__zoom_settings.index(z)].setStyleSheet("")
|
||||
self._buttons[self._zoom_settings.index(z)].setStyleSheet("")
|
||||
|
||||
@@ -3,7 +3,7 @@ from enum import Enum
|
||||
from typing import List, Tuple
|
||||
|
||||
import numpy as np
|
||||
from aarecommon.config.beamline import get_jfjoch_url, mx_beamline, cfg_get
|
||||
from aarecommon.config.beamline import cfg_get, get_jfjoch_url, mx_beamline
|
||||
from aarecommon.config.logger import setup_logger
|
||||
from aarecommon.math.coordinate import Coordinate, SmargonCoordinate
|
||||
from aarecommon.math.find_xtal import compute_crystal_score_array
|
||||
@@ -121,25 +121,25 @@ class RasterGridManager(QObject):
|
||||
def __init__(self, geom: SampleGeometryModel):
|
||||
super().__init__()
|
||||
|
||||
self.__sample_id = -1
|
||||
self.__beamline = mx_beamline()
|
||||
self.__detector_url = get_jfjoch_url(self.__beamline)
|
||||
self._sample_id = -1
|
||||
self._beamline = mx_beamline()
|
||||
self._detector_url = get_jfjoch_url(self._beamline)
|
||||
|
||||
self.__beam_size_mm = geom.beam_size_mm
|
||||
self.__geom = geom
|
||||
self._beam_size_mm = geom.beam_size_mm
|
||||
self._geom = geom
|
||||
|
||||
self.__metric = RasterGridMetric.RASTER_SCORE
|
||||
self._metric = RasterGridMetric.RASTER_SCORE
|
||||
|
||||
self.__loaded_image_prefix = None
|
||||
self.__loaded_image_index = None
|
||||
self._loaded_image_prefix = None
|
||||
self._loaded_image_index = None
|
||||
|
||||
self.__start_point: QPointF = QPointF(0, 0)
|
||||
self.__active_grid: RasterGridRequest = RasterGridRequest(
|
||||
self._start_point: QPointF = QPointF(0, 0)
|
||||
self._active_grid: RasterGridRequest = RasterGridRequest(
|
||||
n_x=0,
|
||||
n_y=0,
|
||||
smargon_top_left=self.__geom.smargon,
|
||||
grid_size_mm=Coordinate(x=0.8 * self.__beam_size_mm.x, y=0.8 * self.__beam_size_mm.y),
|
||||
omega_deg=self.__geom.omega_deg,
|
||||
smargon_top_left=self._geom.smargon,
|
||||
grid_size_mm=Coordinate(x=0.8 * self._beam_size_mm.x, y=0.8 * self._beam_size_mm.y),
|
||||
omega_deg=self._geom.omega_deg,
|
||||
exp_time_s=cfg_get(
|
||||
"daq.data_collection_settings.default_raster_settings.exp_time_s", 0.01
|
||||
),
|
||||
@@ -148,24 +148,24 @@ class RasterGridManager(QObject):
|
||||
),
|
||||
dtz=cfg_get("daq.data_collection_settings.default_raster_settings.dtz", 200.0),
|
||||
)
|
||||
self.__completed_grids: List[CompletedRasterGridElem] = []
|
||||
self._completed_grids: List[CompletedRasterGridElem] = []
|
||||
|
||||
# Cache of pre-rendered heatmap bitmaps, keyed by (id(grid_elem), metric).
|
||||
# Each entry is (QImage, backing ndarray); the ndarray must be kept alive
|
||||
# because QImage shares its buffer without copying. Rebuilt only when the
|
||||
# data or metric changes, not on every repaint (sample move / zoom).
|
||||
self.__heatmap_cache: dict[tuple[int, "RasterGridMetric"], tuple[QImage, np.ndarray]] = {}
|
||||
self._heatmap_cache: dict[tuple[int, "RasterGridMetric"], tuple[QImage, np.ndarray]] = {}
|
||||
|
||||
@property
|
||||
def active_grid(self) -> RasterGridRequest:
|
||||
return self.__active_grid
|
||||
return self._active_grid
|
||||
|
||||
def _is_grid_visible(self, grid: RasterGridRequest):
|
||||
if (
|
||||
grid.visible
|
||||
and abs(normalize_angle(grid.omega_deg - self.__geom.omega_deg)) < 0.2
|
||||
and abs(grid.smargon_top_left.phi_deg - self.__geom.smargon.phi_deg) < 0.2
|
||||
and abs(grid.smargon_top_left.chi_deg - self.__geom.smargon.chi_deg) < 0.2
|
||||
and abs(normalize_angle(grid.omega_deg - self._geom.omega_deg)) < 0.2
|
||||
and abs(grid.smargon_top_left.phi_deg - self._geom.smargon.phi_deg) < 0.2
|
||||
and abs(grid.smargon_top_left.chi_deg - self._geom.smargon.chi_deg) < 0.2
|
||||
and grid.n_x > 0
|
||||
and grid.n_y > 0
|
||||
):
|
||||
@@ -178,15 +178,15 @@ class RasterGridManager(QObject):
|
||||
if not self._is_grid_visible(grid):
|
||||
return None
|
||||
|
||||
cell = grid.grid_size_pxl(self.__geom)
|
||||
cell = grid.grid_size_pxl(self._geom)
|
||||
cell_w = float(cell.x)
|
||||
cell_h = float(cell.y)
|
||||
|
||||
if cell_w <= 0 or cell_h <= 0:
|
||||
return None
|
||||
|
||||
start_anchor = grid.start_pxl(self.__geom)
|
||||
start_picture = self.__geom.sample_to_picture(start_anchor)
|
||||
start_anchor = grid.start_pxl(self._geom)
|
||||
start_picture = self._geom.sample_to_picture(start_anchor)
|
||||
|
||||
return float(start_picture.x), float(start_picture.y), cell_w, cell_h
|
||||
|
||||
@@ -219,91 +219,91 @@ class RasterGridManager(QObject):
|
||||
|
||||
@Slot(RasterGridRequest)
|
||||
def update_active_grid_request(self, grid: RasterGridRequest):
|
||||
self.__active_grid.smargon_top_left = grid.smargon_top_left
|
||||
self.__active_grid.omega_deg = grid.omega_deg
|
||||
self.__active_grid.grid_size_mm = grid.grid_size_mm
|
||||
self.__active_grid.n_x = grid.n_x
|
||||
self.__active_grid.n_y = grid.n_y
|
||||
self._active_grid.smargon_top_left = grid.smargon_top_left
|
||||
self._active_grid.omega_deg = grid.omega_deg
|
||||
self._active_grid.grid_size_mm = grid.grid_size_mm
|
||||
self._active_grid.n_x = grid.n_x
|
||||
self._active_grid.n_y = grid.n_y
|
||||
self.grid_scan_size_changed.emit(
|
||||
self.__active_grid.n_x,
|
||||
self.__active_grid.n_y,
|
||||
self.__active_grid.grid_size_mm.x,
|
||||
self.__active_grid.grid_size_mm.y,
|
||||
self._active_grid.n_x,
|
||||
self._active_grid.n_y,
|
||||
self._active_grid.grid_size_mm.x,
|
||||
self._active_grid.grid_size_mm.y,
|
||||
)
|
||||
|
||||
@Slot(DAQStatusModel)
|
||||
def update_daq_status(self, s: DAQStatusModel):
|
||||
if s.sample is not None and s.sample.db_id != self.__sample_id:
|
||||
self.__sample_id = s.sample.db_id
|
||||
if s.sample is not None and s.sample.db_id != self._sample_id:
|
||||
self._sample_id = s.sample.db_id
|
||||
self.clear_completed_grids()
|
||||
self.clear_active_grid()
|
||||
if self.__beam_size_mm != s.geom.beam_size_mm:
|
||||
self.__beam_size_mm = s.geom.beam_size_mm
|
||||
self.update_grid_size(0.8 * self.__beam_size_mm.x, 0.8 * self.__beam_size_mm.y)
|
||||
self.__geom = s.geom
|
||||
if self._beam_size_mm != s.geom.beam_size_mm:
|
||||
self._beam_size_mm = s.geom.beam_size_mm
|
||||
self.update_grid_size(0.8 * self._beam_size_mm.x, 0.8 * self._beam_size_mm.y)
|
||||
self._geom = s.geom
|
||||
|
||||
def resize_active_grid(self, end_point: QPointF):
|
||||
self.update_active_grid(self.__start_point, end_point)
|
||||
self.update_active_grid(self._start_point, end_point)
|
||||
|
||||
def update_active_grid(self, start: QPointF, end: QPointF):
|
||||
delta_x = (end.x() - start.x()) * self.__geom.pixel_in_mm
|
||||
delta_y = (end.y() - start.y()) * self.__geom.pixel_in_mm
|
||||
delta_x = (end.x() - start.x()) * self._geom.pixel_in_mm
|
||||
delta_y = (end.y() - start.y()) * self._geom.pixel_in_mm
|
||||
|
||||
self.__active_grid.n_x = round(abs(delta_x / self.__active_grid.grid_size_mm.x))
|
||||
self.__active_grid.n_y = round(abs(delta_y / self.__active_grid.grid_size_mm.y))
|
||||
self._active_grid.n_x = round(abs(delta_x / self._active_grid.grid_size_mm.x))
|
||||
self._active_grid.n_y = round(abs(delta_y / self._active_grid.grid_size_mm.y))
|
||||
|
||||
c = self.__geom.picture_to_sample(Coordinate(x=start.x(), y=start.y()))
|
||||
c = self._geom.picture_to_sample(Coordinate(x=start.x(), y=start.y()))
|
||||
|
||||
if delta_x < 0:
|
||||
c.x -= self.__active_grid.n_x * self.__active_grid.grid_size_mm.x
|
||||
c.x -= self._active_grid.n_x * self._active_grid.grid_size_mm.x
|
||||
if delta_y < 0:
|
||||
c.y -= self.__active_grid.n_y * self.__active_grid.grid_size_mm.y
|
||||
c.y -= self._active_grid.n_y * self._active_grid.grid_size_mm.y
|
||||
|
||||
if delta_x < 0 or delta_y < 0:
|
||||
c_pic = self.__geom.sample_to_picture(c)
|
||||
self.__start_point = QPointF(c_pic.x, c_pic.y)
|
||||
c_pic = self._geom.sample_to_picture(c)
|
||||
self._start_point = QPointF(c_pic.x, c_pic.y)
|
||||
else:
|
||||
self.__start_point = start
|
||||
self._start_point = start
|
||||
|
||||
self.__active_grid.smargon_top_left = SmargonCoordinate(
|
||||
sh_mm=self.__geom.beamline_to_smargon(c),
|
||||
phi_deg=self.__geom.smargon.phi_deg,
|
||||
chi_deg=self.__geom.smargon.chi_deg,
|
||||
self._active_grid.smargon_top_left = SmargonCoordinate(
|
||||
sh_mm=self._geom.beamline_to_smargon(c),
|
||||
phi_deg=self._geom.smargon.phi_deg,
|
||||
chi_deg=self._geom.smargon.chi_deg,
|
||||
)
|
||||
|
||||
self.__active_grid.omega_deg = self.__geom.omega_deg
|
||||
self._active_grid.omega_deg = self._geom.omega_deg
|
||||
self.grid_scan_size_changed.emit(
|
||||
self.__active_grid.n_x,
|
||||
self.__active_grid.n_y,
|
||||
self.__active_grid.grid_size_mm.x,
|
||||
self.__active_grid.grid_size_mm.y,
|
||||
self._active_grid.n_x,
|
||||
self._active_grid.n_y,
|
||||
self._active_grid.grid_size_mm.x,
|
||||
self._active_grid.grid_size_mm.y,
|
||||
)
|
||||
|
||||
def move_active_grid(self, delta: QPointF):
|
||||
if not self._is_grid_visible(self.__active_grid):
|
||||
if not self._is_grid_visible(self._active_grid):
|
||||
return
|
||||
|
||||
delta_pxl = Coordinate(x=delta.x(), y=delta.y())
|
||||
delta_mm = self.__geom.smargon_nudge(delta_pxl * self.__geom.pixel_in_mm)
|
||||
delta_mm = self._geom.smargon_nudge(delta_pxl * self._geom.pixel_in_mm)
|
||||
|
||||
self.__active_grid.smargon_top_left = SmargonCoordinate(
|
||||
sh_mm=self.__active_grid.smargon_top_left.sh_mm + delta_mm,
|
||||
phi_deg=self.__geom.smargon.phi_deg,
|
||||
chi_deg=self.__geom.smargon.chi_deg,
|
||||
self._active_grid.smargon_top_left = SmargonCoordinate(
|
||||
sh_mm=self._active_grid.smargon_top_left.sh_mm + delta_mm,
|
||||
phi_deg=self._geom.smargon.phi_deg,
|
||||
chi_deg=self._geom.smargon.chi_deg,
|
||||
)
|
||||
|
||||
@Slot()
|
||||
def clear_active_grid(self):
|
||||
self.__active_grid.n_x = 0
|
||||
self.__active_grid.n_y = 0
|
||||
self._active_grid.n_x = 0
|
||||
self._active_grid.n_y = 0
|
||||
self.grid_scan_size_changed.emit(
|
||||
0, 0, self.__active_grid.grid_size_mm.x, self.__active_grid.grid_size_mm.y
|
||||
0, 0, self._active_grid.grid_size_mm.x, self._active_grid.grid_size_mm.y
|
||||
)
|
||||
|
||||
def get_grid_coord(self, grid: RasterGridRequest, point: QPointF) -> Tuple[int, int]:
|
||||
point_bl = self.__geom.picture_to_sample(Coordinate(x=point.x(), y=point.y()))
|
||||
point_bl = self._geom.picture_to_sample(Coordinate(x=point.x(), y=point.y()))
|
||||
|
||||
delta = point_bl - self.__geom.smargon_to_beamline(grid.smargon_top_left.sh_mm)
|
||||
delta = point_bl - self._geom.smargon_to_beamline(grid.smargon_top_left.sh_mm)
|
||||
|
||||
elem_x = grid.grid_size_mm.x
|
||||
elem_y = grid.grid_size_mm.y
|
||||
@@ -315,7 +315,7 @@ class RasterGridManager(QObject):
|
||||
|
||||
@Slot(QPointF)
|
||||
def load_image(self, point: QPointF):
|
||||
for grid in self.__completed_grids:
|
||||
for grid in self._completed_grids:
|
||||
if self._is_grid_visible(grid.request):
|
||||
n_x = grid.request.n_x
|
||||
n_y = grid.request.n_y
|
||||
@@ -325,11 +325,11 @@ class RasterGridManager(QObject):
|
||||
cell = cell_y * n_x + cell_x
|
||||
|
||||
if (
|
||||
self.__loaded_image_prefix != grid.result.file_prefix
|
||||
or self.__loaded_image_index != grid.result.images[cell].number
|
||||
self._loaded_image_prefix != grid.result.file_prefix
|
||||
or self._loaded_image_index != grid.result.images[cell].number
|
||||
):
|
||||
self.__loaded_image_prefix = grid.result.file_prefix
|
||||
self.__loaded_image_index = grid.result.images[cell].number
|
||||
self._loaded_image_prefix = grid.result.file_prefix
|
||||
self._loaded_image_index = grid.result.images[cell].number
|
||||
logger.debug(
|
||||
f"Load {grid.result.file_prefix} {grid.result.images[cell].number}"
|
||||
)
|
||||
@@ -339,22 +339,20 @@ class RasterGridManager(QObject):
|
||||
# TODO if not in the same PGroup, user can stream from last run only but never load from a file.
|
||||
# If loading a prior run, this shoudl throw an error,
|
||||
# if user in same pgroup, load will always be fine.
|
||||
# self.image_selected.emit(self.__detector_url, grid.result.images[cell].number)
|
||||
# self.image_selected.emit(self._detector_url, grid.result.images[cell].number)
|
||||
|
||||
def is_part_of_active_grid(self, point: QPointF) -> bool:
|
||||
if not self._is_grid_visible(self.__active_grid):
|
||||
if not self._is_grid_visible(self._active_grid):
|
||||
return False
|
||||
point_bl = self.__geom.picture_to_sample(Coordinate(x=point.x(), y=point.y()))
|
||||
point_bl = self._geom.picture_to_sample(Coordinate(x=point.x(), y=point.y()))
|
||||
|
||||
delta = point_bl - self.__geom.smargon_to_beamline(
|
||||
self.__active_grid.smargon_top_left.sh_mm
|
||||
)
|
||||
return (0 <= delta.x < self.__active_grid.n_x * self.__active_grid.grid_size_mm.x) and (
|
||||
0 <= delta.y < self.__active_grid.n_y * self.__active_grid.grid_size_mm.y
|
||||
delta = point_bl - self._geom.smargon_to_beamline(self._active_grid.smargon_top_left.sh_mm)
|
||||
return (0 <= delta.x < self._active_grid.n_x * self._active_grid.grid_size_mm.x) and (
|
||||
0 <= delta.y < self._active_grid.n_y * self._active_grid.grid_size_mm.y
|
||||
)
|
||||
|
||||
def is_part_of_completed_grid(self, point: QPointF) -> str | None:
|
||||
for grid in self.__completed_grids:
|
||||
for grid in self._completed_grids:
|
||||
if self._is_grid_visible(grid.request):
|
||||
n_x = grid.request.n_x
|
||||
n_y = grid.request.n_y
|
||||
@@ -432,25 +430,25 @@ class RasterGridManager(QObject):
|
||||
if (grid_size_mm_x <= 0) or (grid_size_mm_y <= 0):
|
||||
raise ValueError("Grid size must be positive")
|
||||
|
||||
self.__active_grid.n_x = round(
|
||||
self.__active_grid.n_x * self.__active_grid.grid_size_mm.x / grid_size_mm_x
|
||||
self._active_grid.n_x = round(
|
||||
self._active_grid.n_x * self._active_grid.grid_size_mm.x / grid_size_mm_x
|
||||
)
|
||||
self.__active_grid.n_y = round(
|
||||
self.__active_grid.n_y * self.__active_grid.grid_size_mm.y / grid_size_mm_y
|
||||
self._active_grid.n_y = round(
|
||||
self._active_grid.n_y * self._active_grid.grid_size_mm.y / grid_size_mm_y
|
||||
)
|
||||
|
||||
self.__active_grid.grid_size_mm = Coordinate(x=grid_size_mm_x, y=grid_size_mm_y)
|
||||
self._active_grid.grid_size_mm = Coordinate(x=grid_size_mm_x, y=grid_size_mm_y)
|
||||
|
||||
self.grid_scan_size_changed.emit(
|
||||
self.__active_grid.n_x,
|
||||
self.__active_grid.n_y,
|
||||
self.__active_grid.grid_size_mm.x,
|
||||
self.__active_grid.grid_size_mm.y,
|
||||
self._active_grid.n_x,
|
||||
self._active_grid.n_y,
|
||||
self._active_grid.grid_size_mm.x,
|
||||
self._active_grid.grid_size_mm.y,
|
||||
)
|
||||
|
||||
@Slot()
|
||||
def run_grid_scan(self):
|
||||
ag = self.__active_grid
|
||||
ag = self._active_grid
|
||||
grid = RasterGridRequest(
|
||||
dtz=ag.dtz,
|
||||
transmission=ag.transmission,
|
||||
@@ -476,7 +474,7 @@ class RasterGridManager(QObject):
|
||||
|
||||
@Slot()
|
||||
def run_grid_scan_auto(self):
|
||||
ag = self.__active_grid
|
||||
ag = self._active_grid
|
||||
grid = RasterGridRequest(
|
||||
dtz=ag.dtz,
|
||||
transmission=ag.transmission,
|
||||
@@ -506,10 +504,10 @@ class RasterGridManager(QObject):
|
||||
if visible_rect.isEmpty():
|
||||
visible_rect = QRectF()
|
||||
|
||||
self._draw_grid(painter, self.__active_grid, None, alpha, visible_rect)
|
||||
self._draw_grid(painter, self._active_grid, None, alpha, visible_rect)
|
||||
|
||||
for i in self.__completed_grids:
|
||||
match self.__metric:
|
||||
for i in self._completed_grids:
|
||||
match self._metric:
|
||||
case RasterGridMetric.SPOTS_LOW_RES:
|
||||
v = [obj.spots_low_res for obj in i.result.images]
|
||||
case RasterGridMetric.SPOTS:
|
||||
@@ -536,12 +534,12 @@ class RasterGridManager(QObject):
|
||||
v = [self.spot_ice_ratio(obj) for obj in i.result.images]
|
||||
case RasterGridMetric.SPOTS_INDEXED:
|
||||
v = [obj.spots_indexed for obj in i.result.images]
|
||||
self._draw_completed_heatmap(painter, i, v, alpha, visible_rect, (id(i), self.__metric))
|
||||
self._draw_completed_heatmap(painter, i, v, alpha, visible_rect, (id(i), self._metric))
|
||||
|
||||
def _invalidate_heatmap_cache(self) -> None:
|
||||
"""Drop all cached heatmap bitmaps. Call whenever the completed-grid data
|
||||
set or the active metric changes; cheap to rebuild lazily on next paint."""
|
||||
self.__heatmap_cache.clear()
|
||||
self._heatmap_cache.clear()
|
||||
|
||||
def _heatmap_image(
|
||||
self, cache_key: tuple, grid: RasterGridRequest, values: List[float] | List[int]
|
||||
@@ -550,7 +548,7 @@ class RasterGridManager(QObject):
|
||||
on a cache miss. One pixel per cell; colours baked at full opacity with
|
||||
the alpha channel encoding validity (overlay transparency is applied at
|
||||
blit time via painter opacity, so it never invalidates the cache)."""
|
||||
cached = self.__heatmap_cache.get(cache_key)
|
||||
cached = self._heatmap_cache.get(cache_key)
|
||||
if cached is not None:
|
||||
return cached[0]
|
||||
|
||||
@@ -558,7 +556,7 @@ class RasterGridManager(QObject):
|
||||
if built is None:
|
||||
return None
|
||||
|
||||
self.__heatmap_cache[cache_key] = built
|
||||
self._heatmap_cache[cache_key] = built
|
||||
return built[0]
|
||||
|
||||
def _build_heatmap_image(
|
||||
@@ -875,28 +873,28 @@ class RasterGridManager(QObject):
|
||||
|
||||
@Slot(float)
|
||||
def update_exposure_time(self, exp_time_s: float):
|
||||
self.__active_grid.exp_time_s = exp_time_s
|
||||
self._active_grid.exp_time_s = exp_time_s
|
||||
|
||||
@Slot(float)
|
||||
def update_transmission(self, transmission: float):
|
||||
self.__active_grid.transmission = transmission
|
||||
self._active_grid.transmission = transmission
|
||||
|
||||
@Slot(float)
|
||||
def update_dtz(self, dtz: float):
|
||||
self.__active_grid.dtz = dtz
|
||||
self._active_grid.dtz = dtz
|
||||
|
||||
@Slot(str)
|
||||
def update_filename(self, filename: str):
|
||||
self.__active_grid.file_prefix = filename
|
||||
self._active_grid.file_prefix = filename
|
||||
|
||||
@Slot(RasterGridMetric)
|
||||
def metric(self, r: RasterGridMetric):
|
||||
self.__metric = r
|
||||
self._metric = r
|
||||
self._invalidate_heatmap_cache()
|
||||
|
||||
@Slot()
|
||||
def clear_completed_grids(self):
|
||||
self.__completed_grids.clear()
|
||||
self._completed_grids.clear()
|
||||
self._invalidate_heatmap_cache()
|
||||
self.completed_grid_updated.emit()
|
||||
|
||||
@@ -904,50 +902,50 @@ class RasterGridManager(QObject):
|
||||
def grid_scan_completed(self, raster_list: CompletedRasterGrid):
|
||||
last_raster = None
|
||||
for raster in raster_list.r:
|
||||
self.__completed_grids.append(raster)
|
||||
self._completed_grids.append(raster)
|
||||
last_raster = raster
|
||||
self.__active_grid.n_x = 0
|
||||
self.__active_grid.n_y = 0
|
||||
self._active_grid.n_x = 0
|
||||
self._active_grid.n_y = 0
|
||||
self._invalidate_heatmap_cache()
|
||||
if last_raster is not None and last_raster.result.file_prefix is not None:
|
||||
com = last_raster.centre_of_mass
|
||||
logger.info(f"COM: {com}")
|
||||
cell = com.max_image if com else 0
|
||||
self.image_selected.emit(self.__detector_url, cell)
|
||||
self.image_selected.emit(self._detector_url, cell)
|
||||
self.completed_grid_updated.emit()
|
||||
|
||||
@Slot(int, bool)
|
||||
def completed_grid_scan_toggle_visible(self, row: int, visible: bool):
|
||||
if 0 <= row < len(self.__completed_grids):
|
||||
self.__completed_grids[row].request.visible = visible
|
||||
if 0 <= row < len(self._completed_grids):
|
||||
self._completed_grids[row].request.visible = visible
|
||||
self.completed_grid_updated.emit()
|
||||
|
||||
@Slot(int)
|
||||
def completed_grid_scan_delete(self, row: int):
|
||||
if 0 <= row < len(self.__completed_grids):
|
||||
del self.__completed_grids[row]
|
||||
if 0 <= row < len(self._completed_grids):
|
||||
del self._completed_grids[row]
|
||||
self._invalidate_heatmap_cache()
|
||||
self.completed_grid_updated.emit()
|
||||
|
||||
@Slot(int)
|
||||
def completed_grid_scan_goto(self, row: int):
|
||||
if 0 <= row < len(self.__completed_grids):
|
||||
self.omega.emit(self.__completed_grids[row].request.omega_deg)
|
||||
if 0 <= row < len(self._completed_grids):
|
||||
self.omega.emit(self._completed_grids[row].request.omega_deg)
|
||||
self.smargon.emit(
|
||||
SmargonCoordinate(
|
||||
phi_deg=self.__completed_grids[row].request.smargon_top_left.phi_deg,
|
||||
chi_deg=self.__completed_grids[row].request.smargon_top_left.chi_deg,
|
||||
phi_deg=self._completed_grids[row].request.smargon_top_left.phi_deg,
|
||||
chi_deg=self._completed_grids[row].request.smargon_top_left.chi_deg,
|
||||
)
|
||||
)
|
||||
|
||||
@Slot(int)
|
||||
def completed_grid_redo(self, row: int):
|
||||
if 0 <= row < len(self.__completed_grids):
|
||||
src = self.__completed_grids[row].request
|
||||
self.__active_grid.n_x = src.n_x
|
||||
self.__active_grid.n_y = src.n_y
|
||||
self.__active_grid.grid_size_mm = Coordinate(x=src.grid_size_mm.x, y=src.grid_size_mm.y)
|
||||
self.__active_grid.smargon_top_left = SmargonCoordinate(
|
||||
if 0 <= row < len(self._completed_grids):
|
||||
src = self._completed_grids[row].request
|
||||
self._active_grid.n_x = src.n_x
|
||||
self._active_grid.n_y = src.n_y
|
||||
self._active_grid.grid_size_mm = Coordinate(x=src.grid_size_mm.x, y=src.grid_size_mm.y)
|
||||
self._active_grid.smargon_top_left = SmargonCoordinate(
|
||||
sh_mm=Coordinate(
|
||||
x=src.smargon_top_left.sh_mm.x,
|
||||
y=src.smargon_top_left.sh_mm.y,
|
||||
@@ -956,18 +954,18 @@ class RasterGridManager(QObject):
|
||||
phi_deg=src.smargon_top_left.phi_deg,
|
||||
chi_deg=src.smargon_top_left.chi_deg,
|
||||
)
|
||||
self.__active_grid.omega_deg = src.omega_deg
|
||||
self._active_grid.omega_deg = src.omega_deg
|
||||
|
||||
self.__active_grid.visible = True
|
||||
self.__completed_grids[row].request.visible = False
|
||||
self._active_grid.visible = True
|
||||
self._completed_grids[row].request.visible = False
|
||||
|
||||
self.grid_scan_size_changed.emit(
|
||||
self.__active_grid.n_x,
|
||||
self.__active_grid.n_y,
|
||||
self.__active_grid.grid_size_mm.x,
|
||||
self.__active_grid.grid_size_mm.y,
|
||||
self._active_grid.n_x,
|
||||
self._active_grid.n_y,
|
||||
self._active_grid.grid_size_mm.x,
|
||||
self._active_grid.grid_size_mm.y,
|
||||
)
|
||||
self.completed_grid_updated.emit()
|
||||
|
||||
def get_completed_grids(self) -> List[CompletedRasterGridElem]:
|
||||
return self.__completed_grids
|
||||
return self._completed_grids
|
||||
|
||||
@@ -8,10 +8,10 @@ class RotationScanManager(QObject):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.__beamline = mx_beamline()
|
||||
self.__detector_url = get_jfjoch_url(self.__beamline)
|
||||
self._beamline = mx_beamline()
|
||||
self._detector_url = get_jfjoch_url(self._beamline)
|
||||
|
||||
@Slot(CompletedRotationScan)
|
||||
def scan_completed(self, r: CompletedRotationScan):
|
||||
if r.result.file_prefix is not None:
|
||||
self.file_ready.emit(self.__detector_url, 0)
|
||||
self.file_ready.emit(self._detector_url, 0)
|
||||
|
||||
@@ -7,11 +7,11 @@ class SampleMountLogic(QObject):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.__sample = None
|
||||
self._sample = None
|
||||
|
||||
@Slot(DAQStatusModel)
|
||||
def update_daq_status(self, s: DAQStatusModel):
|
||||
if s.sample != self.__sample:
|
||||
if s.sample != self._sample:
|
||||
self.sample_changed.emit(s.sample)
|
||||
|
||||
self.__sample = s.sample
|
||||
self._sample = s.sample
|
||||
|
||||
@@ -21,68 +21,68 @@ class SampleCameraThread(QThread):
|
||||
def __init__(self, zmq_url: str, parent=None):
|
||||
super().__init__(parent)
|
||||
context = zmq.Context()
|
||||
self.__socket = context.socket(zmq.SUB)
|
||||
self.__socket.setsockopt(zmq.SUBSCRIBE, b"")
|
||||
self.__socket.setsockopt(zmq.RCVTIMEO, 500)
|
||||
self.__socket.connect(zmq_url)
|
||||
self._socket = context.socket(zmq.SUB)
|
||||
self._socket.setsockopt(zmq.SUBSCRIBE, b"")
|
||||
self._socket.setsockopt(zmq.RCVTIMEO, 500)
|
||||
self._socket.connect(zmq_url)
|
||||
|
||||
self.running = True
|
||||
self.__measure_focus = False
|
||||
self.__focus_mask = None
|
||||
self.__beam_x = 0
|
||||
self.__beam_y = 0
|
||||
self.__last_beam_pos = None
|
||||
self.__radius = 40
|
||||
self._measure_focus = False
|
||||
self._focus_mask = None
|
||||
self._beam_x = 0
|
||||
self._beam_y = 0
|
||||
self._last_beam_pos = None
|
||||
self._radius = 40
|
||||
|
||||
self.__fps_window_start = time.perf_counter()
|
||||
self.__fps_frame_count = 0
|
||||
self.__fps_emit_period_s = 0.5
|
||||
self.__last_frame_time = None
|
||||
self.__no_frame_timeout_s = 5.0
|
||||
self._fps_window_start = time.perf_counter()
|
||||
self._fps_frame_count = 0
|
||||
self._fps_emit_period_s = 0.5
|
||||
self._last_frame_time = None
|
||||
self._no_frame_timeout_s = 5.0
|
||||
|
||||
self.__camera_available = False
|
||||
self.__last_camera_error: str | None = None
|
||||
self._camera_available = False
|
||||
self._last_camera_error: str | None = None
|
||||
|
||||
def __set_camera_available(self, available: bool, error: str | None = None) -> None:
|
||||
if available != self.__camera_available:
|
||||
self.__camera_available = available
|
||||
def _set_camera_available(self, available: bool, error: str | None = None) -> None:
|
||||
if available != self._camera_available:
|
||||
self._camera_available = available
|
||||
self.camera_availability_changed.emit(available)
|
||||
|
||||
if error is not None and error != self.__last_camera_error:
|
||||
self.__last_camera_error = error
|
||||
if error is not None and error != self._last_camera_error:
|
||||
self._last_camera_error = error
|
||||
self.camera_error.emit(error)
|
||||
|
||||
if available:
|
||||
self.__last_camera_error = None
|
||||
self._last_camera_error = None
|
||||
|
||||
@Slot(DAQStatusModel)
|
||||
def update_daq_status(self, s: DAQStatusModel):
|
||||
self.__beam_x = s.geom.beam_location_pxl.x
|
||||
self.__beam_y = s.geom.beam_location_pxl.y
|
||||
if (self.__beam_x, self.__beam_y) != self.__last_beam_pos:
|
||||
self.__focus_mask = None # Invalidate cache
|
||||
self.__last_beam_pos = (self.__beam_x, self.__beam_y)
|
||||
self._beam_x = s.geom.beam_location_pxl.x
|
||||
self._beam_y = s.geom.beam_location_pxl.y
|
||||
if (self._beam_x, self._beam_y) != self._last_beam_pos:
|
||||
self._focus_mask = None # Invalidate cache
|
||||
self._last_beam_pos = (self._beam_x, self._beam_y)
|
||||
|
||||
@Slot(bool)
|
||||
def enable_focus_measurement(self, enabled: bool = True):
|
||||
"""Enable or disable focus measurement."""
|
||||
self.__measure_focus = enabled
|
||||
self._measure_focus = enabled
|
||||
|
||||
def run(self):
|
||||
while self.running:
|
||||
try:
|
||||
r = self.__socket.recv_multipart()
|
||||
r = self._socket.recv_multipart()
|
||||
|
||||
now = time.perf_counter()
|
||||
self.__last_frame_time = now
|
||||
self.__fps_frame_count += 1
|
||||
self._last_frame_time = now
|
||||
self._fps_frame_count += 1
|
||||
|
||||
elapsed = now - self.__fps_window_start
|
||||
if elapsed >= self.__fps_emit_period_s:
|
||||
fps = self.__fps_frame_count / elapsed if elapsed > 0 else 0.0
|
||||
elapsed = now - self._fps_window_start
|
||||
if elapsed >= self._fps_emit_period_s:
|
||||
fps = self._fps_frame_count / elapsed if elapsed > 0 else 0.0
|
||||
self.fps_measure.emit(float(fps))
|
||||
self.__fps_window_start = now
|
||||
self.__fps_frame_count = 0
|
||||
self._fps_window_start = now
|
||||
self._fps_frame_count = 0
|
||||
|
||||
if len(r) < 2:
|
||||
continue
|
||||
@@ -106,7 +106,7 @@ class SampleCameraThread(QThread):
|
||||
encoded = np.frombuffer(data, dtype=np.uint8)
|
||||
bgr = cv2.imdecode(encoded, cv2.IMREAD_COLOR)
|
||||
if bgr is None:
|
||||
self.__set_camera_available(
|
||||
self._set_camera_available(
|
||||
False, "Sample camera feed unavailable: failed to decode JPEG frame"
|
||||
)
|
||||
continue
|
||||
@@ -116,24 +116,24 @@ class SampleCameraThread(QThread):
|
||||
raw = np.frombuffer(data, np.uint8).reshape((h, w))
|
||||
rgb = cv2.cvtColor(raw, cv2.COLOR_BAYER_GB2RGB)
|
||||
else:
|
||||
self.__set_camera_available(
|
||||
self._set_camera_available(
|
||||
False,
|
||||
f"Sample camera feed unavailable: unsupported frame header {header}",
|
||||
)
|
||||
continue
|
||||
|
||||
self.__set_camera_available(True)
|
||||
self._set_camera_available(True)
|
||||
|
||||
if self.__measure_focus:
|
||||
if self._measure_focus:
|
||||
gray = cv2.cvtColor(rgb, cv2.COLOR_RGB2GRAY)
|
||||
if self.__focus_mask is None or self.__focus_mask.shape != gray.shape:
|
||||
if self._focus_mask is None or self._focus_mask.shape != gray.shape:
|
||||
height, width = gray.shape
|
||||
y, x = np.ogrid[:height, :width]
|
||||
self.__focus_mask = (x - self.__beam_x) ** 2 + (
|
||||
y - self.__beam_y
|
||||
) ** 2 <= self.__radius**2
|
||||
self._focus_mask = (x - self._beam_x) ** 2 + (
|
||||
y - self._beam_y
|
||||
) ** 2 <= self._radius**2
|
||||
|
||||
sharpness = focus_measure_edges(gray, self.__focus_mask)
|
||||
sharpness = focus_measure_edges(gray, self._focus_mask)
|
||||
self.focus_measure.emit(sharpness)
|
||||
|
||||
qimage = QImage(
|
||||
@@ -141,33 +141,33 @@ class SampleCameraThread(QThread):
|
||||
).copy()
|
||||
self.camera_image.emit(QPixmap.fromImage(qimage))
|
||||
else:
|
||||
self.__set_camera_available(
|
||||
self._set_camera_available(
|
||||
False, "Sample camera feed unavailable: no frame header in zmq stream"
|
||||
)
|
||||
except zmq.Again: # Timeout occurred
|
||||
now = time.perf_counter()
|
||||
elapsed = now - self.__fps_window_start
|
||||
if elapsed >= self.__fps_emit_period_s:
|
||||
elapsed = now - self._fps_window_start
|
||||
if elapsed >= self._fps_emit_period_s:
|
||||
no_frames_long = (
|
||||
self.__last_frame_time is None
|
||||
or (now - self.__last_frame_time) >= self.__no_frame_timeout_s
|
||||
self._last_frame_time is None
|
||||
or (now - self._last_frame_time) >= self._no_frame_timeout_s
|
||||
)
|
||||
self.fps_measure.emit(float("nan") if no_frames_long else 0.0)
|
||||
self.__fps_window_start = now
|
||||
self.__fps_frame_count = 0
|
||||
self._fps_window_start = now
|
||||
self._fps_frame_count = 0
|
||||
|
||||
if no_frames_long:
|
||||
self.__set_camera_available(False, "Sample camera feed unavailable")
|
||||
self._set_camera_available(False, "Sample camera feed unavailable")
|
||||
continue # Check self.running again
|
||||
except Exception as e:
|
||||
self.__set_camera_available(False, f"Sample camera feed unavailable: {e}")
|
||||
self._set_camera_available(False, f"Sample camera feed unavailable: {e}")
|
||||
self.running = False
|
||||
|
||||
def stop(self):
|
||||
self.running = False
|
||||
|
||||
if self.__socket:
|
||||
self.__socket.close()
|
||||
if self._socket:
|
||||
self._socket.close()
|
||||
|
||||
if self.isRunning():
|
||||
self.quit()
|
||||
|
||||
+191
-191
@@ -142,15 +142,15 @@ class DAQWorker(QObject):
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self._active_status_error_key = None
|
||||
self.__token = token
|
||||
self.__base_url = base_url
|
||||
self.__net_manager = QNetworkAccessManager()
|
||||
self.__net_manager.sslErrors.connect(self._handle_ssl_errors)
|
||||
self.__timer = QTimer()
|
||||
self.__timer.setInterval(500)
|
||||
self.__timer.timeout.connect(self.regular_update)
|
||||
self.__timer.start()
|
||||
self.__counter = 0
|
||||
self._token = token
|
||||
self._base_url = base_url
|
||||
self._net_manager = QNetworkAccessManager()
|
||||
self._net_manager.sslErrors.connect(self._handle_ssl_errors)
|
||||
self._timer = QTimer()
|
||||
self._timer.setInterval(500)
|
||||
self._timer.timeout.connect(self.regular_update)
|
||||
self._timer.start()
|
||||
self._counter = 0
|
||||
self._automation_progress_buffer = ""
|
||||
self._cleanup_done = False
|
||||
|
||||
@@ -215,7 +215,7 @@ class DAQWorker(QObject):
|
||||
self._local_contact_metadata_poll_enabled = True
|
||||
self._local_contact_metadata_error: str | None = None
|
||||
|
||||
if self.__base_url is not None:
|
||||
if self._base_url is not None:
|
||||
self.start_face_detection_stream()
|
||||
self.start_baton_stream()
|
||||
self.start_automation_progress_stream()
|
||||
@@ -333,17 +333,17 @@ class DAQWorker(QObject):
|
||||
"""
|
||||
Periodically triggered slot to update spreadsheet and request status from server.
|
||||
"""
|
||||
if self.__counter % SPREADHSEET_FREQUENCY == 0:
|
||||
if self._counter % SPREADHSEET_FREQUENCY == 0:
|
||||
self.load_spreadsheet()
|
||||
self.load_reference_tools()
|
||||
self.__counter = (self.__counter + 1) % SPREADHSEET_FREQUENCY
|
||||
self._counter = (self._counter + 1) % SPREADHSEET_FREQUENCY
|
||||
self.send_status_request()
|
||||
|
||||
def send_status_request(self):
|
||||
"""
|
||||
Send an asynchronous HTTP GET request to the /status endpoint.
|
||||
"""
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
return
|
||||
|
||||
now = time.monotonic()
|
||||
@@ -351,16 +351,16 @@ class DAQWorker(QObject):
|
||||
return
|
||||
self._last_status_request_ts = now
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/status"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/status"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(request)
|
||||
reply.finished.connect(lambda: self.handle_status_response(reply))
|
||||
|
||||
def server_about_info(self) -> tuple[str, str]:
|
||||
version_request = QNetworkRequest(QUrl(f"{self.__base_url}/about/running_version"))
|
||||
version_reply = self.__net_manager.get(version_request)
|
||||
file_request = QNetworkRequest(QUrl(f"{self.__base_url}/about/server_file_path"))
|
||||
file_reply = self.__net_manager.get(file_request)
|
||||
version_request = QNetworkRequest(QUrl(f"{self._base_url}/about/running_version"))
|
||||
version_reply = self._net_manager.get(version_request)
|
||||
file_request = QNetworkRequest(QUrl(f"{self._base_url}/about/server_file_path"))
|
||||
file_reply = self._net_manager.get(file_request)
|
||||
version = (
|
||||
str(version_reply.readAll()) if version_reply.waitForReadyRead(500) else "Not connected"
|
||||
)
|
||||
@@ -734,15 +734,15 @@ class DAQWorker(QObject):
|
||||
url: The relative URL (endpoint).
|
||||
body: The request body string.
|
||||
"""
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info(f"POST /{url}: {body}")
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/{url}"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/{url}"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
if str:
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
reply = self.__net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply = self._net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply.finished.connect(lambda: self.handle_req_response(reply))
|
||||
|
||||
def generic_put(self, url: str, body: str = ""):
|
||||
@@ -753,14 +753,14 @@ class DAQWorker(QObject):
|
||||
url: The relative URL (endpoint).
|
||||
body: The request body string.
|
||||
"""
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info(f"PUT /{url}: {body}")
|
||||
return
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/{url}"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/{url}"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
if str:
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
reply = self.__net_manager.put(request, QByteArray(body.encode("utf-8")))
|
||||
reply = self._net_manager.put(request, QByteArray(body.encode("utf-8")))
|
||||
reply.finished.connect(lambda: self.handle_req_response(reply))
|
||||
|
||||
def generic_delete(self, url: str):
|
||||
@@ -770,13 +770,13 @@ class DAQWorker(QObject):
|
||||
Args:
|
||||
url: The relative URL (endpoint).
|
||||
"""
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info(f"DELETE /{url}")
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/{url}"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.deleteResource(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/{url}"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.deleteResource(request)
|
||||
reply.finished.connect(lambda: self.handle_req_response(reply))
|
||||
|
||||
@Slot(float)
|
||||
@@ -869,45 +869,45 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot(str)
|
||||
def free_beamline(self, confirmation_code: str):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info("POST /state/free_beamline")
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/state/free_beamline"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/state/free_beamline"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
body = json.dumps({"confirmation_code": confirmation_code})
|
||||
reply = self.__net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply = self._net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply.finished.connect(
|
||||
lambda: self._handle_recovery_action_response(reply, "Beamline busy flag cleared.")
|
||||
)
|
||||
|
||||
@Slot(str)
|
||||
def take_over_beamline(self, confirmation_code: str):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info("POST /access/take_over_beamline")
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/access/take_over_beamline"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/access/take_over_beamline"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
body = json.dumps({"confirmation_code": confirmation_code})
|
||||
reply = self.__net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply = self._net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply.finished.connect(
|
||||
lambda: self._handle_recovery_action_response(reply, "Beamline session taken over.")
|
||||
)
|
||||
|
||||
@Slot(str)
|
||||
def recover_beamline(self, confirmation_code: str):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info("POST /recovery/recover_beamline")
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/recovery/recover_beamline"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/recovery/recover_beamline"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
body = json.dumps({"confirmation_code": confirmation_code})
|
||||
reply = self.__net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply = self._net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply.finished.connect(
|
||||
lambda: self._handle_recovery_action_response(
|
||||
reply, "Beamline recovered to Maintenance."
|
||||
@@ -916,15 +916,15 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot(str)
|
||||
def recovery_unmount_sample(self, confirmation_code: str):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info("POST /recovery/unmount_sample")
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/recovery/unmount_sample"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/recovery/unmount_sample"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
body = json.dumps({"confirmation_code": confirmation_code})
|
||||
reply = self.__net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply = self._net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply.finished.connect(
|
||||
lambda: self._handle_recovery_action_response(reply, "Recovery unmount completed.")
|
||||
)
|
||||
@@ -960,13 +960,13 @@ class DAQWorker(QObject):
|
||||
@Slot()
|
||||
def get_all_pgroups(self):
|
||||
logger.debug("PUT /access/all_pgroups")
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
self.staff_pgroups_loaded.emit([])
|
||||
return
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/access/all_pgroups"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/access/all_pgroups"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
reply = self.__net_manager.put(request, QByteArray(b""))
|
||||
reply = self._net_manager.put(request, QByteArray(b""))
|
||||
reply.finished.connect(lambda: self._handle_all_pgroups_response(reply))
|
||||
|
||||
@Slot(SampleCameraSettings)
|
||||
@@ -1032,11 +1032,11 @@ class DAQWorker(QObject):
|
||||
"""
|
||||
self.run_number_incremented.emit()
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/scan/rotation"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/scan/rotation"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
body = r.model_dump_json()
|
||||
reply = self.__net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply = self._net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply.finished.connect(lambda: self.handle_rotation_scan_response(reply))
|
||||
|
||||
def handle_raster_scan_response(self, reply: QNetworkReply):
|
||||
@@ -1086,7 +1086,7 @@ class DAQWorker(QObject):
|
||||
r: The RasterGridRequest object.
|
||||
"""
|
||||
self.run_number_incremented.emit()
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info(f"POST /scan/raster: {r.model_dump_json()}")
|
||||
image_number = r.get_image_number()
|
||||
new_copy = copy.deepcopy(r)
|
||||
@@ -1113,11 +1113,11 @@ class DAQWorker(QObject):
|
||||
self.raster_scan_completed.emit(reply)
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/scan/raster?auto_center=false"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/scan/raster?auto_center=false"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
body = r.model_dump_json()
|
||||
reply = self.__net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply = self._net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply.finished.connect(lambda: self.handle_raster_scan_response(reply))
|
||||
|
||||
@Slot(RasterGridRequest)
|
||||
@@ -1129,7 +1129,7 @@ class DAQWorker(QObject):
|
||||
r: The RasterGridRequest object.
|
||||
"""
|
||||
self.run_number_incremented.emit()
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info(f"POST /scan/raster: {r.model_dump_json()}")
|
||||
image_number = r.get_image_number()
|
||||
new_copy = copy.deepcopy(r)
|
||||
@@ -1155,32 +1155,32 @@ class DAQWorker(QObject):
|
||||
self.raster_scan_completed.emit(reply)
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/scan/raster?auto_center=true"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/scan/raster?auto_center=true"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
body = r.model_dump_json()
|
||||
reply = self.__net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply = self._net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply.finished.connect(lambda: self.handle_raster_scan_response(reply))
|
||||
|
||||
@Slot()
|
||||
def load_spreadsheet(self):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info("GET /sample/spreadsheet")
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/sample/spreadsheet"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/sample/spreadsheet"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(request)
|
||||
reply.finished.connect(lambda: self.handle_spreadsheet_response(reply))
|
||||
|
||||
@Slot()
|
||||
def load_reference_tools(self):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info("GET /sample/reference_tools")
|
||||
return
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/sample/reference_tools"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/sample/reference_tools"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(request)
|
||||
reply.finished.connect(lambda: self.handle_reference_tools_response(reply))
|
||||
|
||||
def _emit_manual_collection_critical_failure(self, msg: str) -> None:
|
||||
@@ -1372,20 +1372,20 @@ class DAQWorker(QObject):
|
||||
Args:
|
||||
s: The SampleShortInfo object.
|
||||
"""
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info(f"POST /scan/auto: {s.model_dump_json()}")
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/scan/auto"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/scan/auto"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
body = s.model_dump_json()
|
||||
reply = self.__net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply = self._net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply.finished.connect(lambda: self.handle_auto_scan_response(reply, s.db_id))
|
||||
|
||||
@Slot(SimpleScanParameters)
|
||||
def smart_params(self, p: SimpleScanParameters):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info(f"POST /scan/smart_params: {p.model_dump_json()}")
|
||||
return
|
||||
self.generic_post("scan/smart_params", p.model_dump_json())
|
||||
@@ -1420,26 +1420,26 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot()
|
||||
def resync_sample(self):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info("POST /sample/resync")
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/sample/resync"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/sample/resync"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
reply = self.__net_manager.post(request, QByteArray(b""))
|
||||
reply = self._net_manager.post(request, QByteArray(b""))
|
||||
reply.finished.connect(lambda: self._handle_sample_resync_response(reply))
|
||||
|
||||
@Slot()
|
||||
def resync_local_contact_detector_metadata(self):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info("POST /local_contact/resync/detector_metadata")
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/local_contact/resync/detector_metadata"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/local_contact/resync/detector_metadata"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
reply = self.__net_manager.post(request, QByteArray(b""))
|
||||
reply = self._net_manager.post(request, QByteArray(b""))
|
||||
reply.finished.connect(lambda: self._handle_detector_metadata_resync_response(reply))
|
||||
|
||||
@Slot(float)
|
||||
@@ -1486,7 +1486,7 @@ class DAQWorker(QObject):
|
||||
if not self._local_contact_metadata_poll_enabled:
|
||||
return
|
||||
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
self.local_contact_simulation_state_loaded.emit(
|
||||
{
|
||||
"bec": False,
|
||||
@@ -1498,9 +1498,9 @@ class DAQWorker(QObject):
|
||||
)
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/local_contact/simulation_state"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/local_contact/simulation_state"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(request)
|
||||
reply.finished.connect(lambda: self._handle_local_contact_simulation_state_response(reply))
|
||||
|
||||
def _handle_local_contact_simulation_state_response(self, reply: QNetworkReply):
|
||||
@@ -1523,13 +1523,13 @@ class DAQWorker(QObject):
|
||||
if not self._local_contact_metadata_poll_enabled:
|
||||
return
|
||||
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
self.local_contact_device_state_loaded.emit({})
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/local_contact/device_state"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/local_contact/device_state"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(request)
|
||||
reply.finished.connect(lambda: self._handle_local_contact_device_state_response(reply))
|
||||
|
||||
def _handle_local_contact_device_state_response(self, reply: QNetworkReply):
|
||||
@@ -1552,13 +1552,13 @@ class DAQWorker(QObject):
|
||||
if not self._local_contact_metadata_poll_enabled:
|
||||
return
|
||||
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
self.local_contact_links_loaded.emit({})
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/local_contact/links"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/local_contact/links"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(request)
|
||||
reply.finished.connect(lambda: self._handle_local_contact_links_response(reply))
|
||||
|
||||
def _handle_local_contact_links_response(self, reply: QNetworkReply):
|
||||
@@ -1578,7 +1578,7 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot()
|
||||
def load_local_contact_config(self):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
self.local_contact_config_loaded.emit(
|
||||
{
|
||||
"mount_to_center_sleep_s": 0.0,
|
||||
@@ -1588,9 +1588,9 @@ class DAQWorker(QObject):
|
||||
)
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/local_contact/config"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/local_contact/config"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(request)
|
||||
reply.finished.connect(lambda: self._handle_local_contact_config_response(reply))
|
||||
|
||||
def _handle_local_contact_config_response(self, reply: QNetworkReply):
|
||||
@@ -1610,15 +1610,15 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot(dict)
|
||||
def set_local_contact_config(self, payload: dict):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
self.local_contact_config_saved.emit(payload)
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/local_contact/config"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/local_contact/config"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
body = QByteArray(json.dumps(payload).encode("utf-8"))
|
||||
reply = self.__net_manager.put(request, body)
|
||||
reply = self._net_manager.put(request, body)
|
||||
reply.finished.connect(lambda: self._handle_set_local_contact_config_response(reply))
|
||||
|
||||
def _handle_set_local_contact_config_response(self, reply: QNetworkReply):
|
||||
@@ -1651,14 +1651,14 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot()
|
||||
def bec_list_all_user_macros(self):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info("GET /bec/user_macros")
|
||||
self.bec_user_macros_loaded.emit([])
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/bec/user_macros"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/bec/user_macros"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(request)
|
||||
reply.finished.connect(lambda: self._handle_bec_user_macros_response(reply))
|
||||
|
||||
def _handle_bec_user_macros_response(self, reply: QNetworkReply):
|
||||
@@ -1674,14 +1674,14 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot()
|
||||
def bec_list_all_devices(self):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info("GET /bec/devices")
|
||||
self.bec_devices_loaded.emit([])
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/bec/devices"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/bec/devices"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(request)
|
||||
reply.finished.connect(lambda: self._handle_bec_devices_response(reply))
|
||||
|
||||
def _handle_bec_devices_response(self, reply: QNetworkReply):
|
||||
@@ -1763,13 +1763,13 @@ class DAQWorker(QObject):
|
||||
"""
|
||||
Request an ML-based bounding box for the current sample.
|
||||
"""
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info("POST /alc/ml_bounding_box")
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/alc/ml_bounding_box"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.post(request, QByteArray(b""))
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/alc/ml_bounding_box"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.post(request, QByteArray(b""))
|
||||
reply.finished.connect(lambda: self.handle_ml_box_response(reply))
|
||||
|
||||
def _handle_face_detection_response(self, reply: QNetworkReply):
|
||||
@@ -1818,7 +1818,7 @@ class DAQWorker(QObject):
|
||||
)
|
||||
return
|
||||
|
||||
if self.__base_url is not None:
|
||||
if self._base_url is not None:
|
||||
QTimer.singleShot(1000, self.start_face_detection_stream)
|
||||
|
||||
@staticmethod
|
||||
@@ -1990,11 +1990,11 @@ class DAQWorker(QObject):
|
||||
)
|
||||
return
|
||||
|
||||
if self.__base_url is not None:
|
||||
if self._base_url is not None:
|
||||
QTimer.singleShot(1000, self.start_automation_progress_stream)
|
||||
|
||||
def start_automation_progress_stream(self):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
return
|
||||
|
||||
if self._automation_progress_stream_reply is not None:
|
||||
@@ -2005,15 +2005,15 @@ class DAQWorker(QObject):
|
||||
|
||||
self._automation_progress_buffer = ""
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/sse/automation_progress"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/sse/automation_progress"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(request)
|
||||
reply.readyRead.connect(lambda: self._read_automation_progress_stream(reply))
|
||||
reply.finished.connect(self._restart_automation_progress_stream)
|
||||
self._automation_progress_stream_reply = reply
|
||||
|
||||
def start_face_detection_stream(self):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
return
|
||||
|
||||
if self._face_detection_stream_reply is not None:
|
||||
@@ -2022,9 +2022,9 @@ class DAQWorker(QObject):
|
||||
if self._face_detection_stream_blocked_403:
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/sse/face_detection"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/sse/face_detection"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(request)
|
||||
reply.readyRead.connect(lambda: self._read_face_detection_stream(reply))
|
||||
reply.finished.connect(self._restart_face_detection_stream)
|
||||
self._face_detection_stream_reply = reply
|
||||
@@ -2038,16 +2038,16 @@ class DAQWorker(QObject):
|
||||
steps: Number of rotation steps.
|
||||
step_size: Degrees per step.
|
||||
"""
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info(f"POST /face_detection/run?steps={steps}&step_size={step_size}")
|
||||
return
|
||||
|
||||
request = QNetworkRequest(
|
||||
QUrl(f"{self.__base_url}/face_detection/run?steps={steps}&step_size={step_size}")
|
||||
QUrl(f"{self._base_url}/face_detection/run?steps={steps}&step_size={step_size}")
|
||||
)
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
reply = self.__net_manager.post(request, QByteArray(b""))
|
||||
reply = self._net_manager.post(request, QByteArray(b""))
|
||||
reply.finished.connect(lambda: self._handle_face_detection_response(reply))
|
||||
|
||||
@Slot()
|
||||
@@ -2070,9 +2070,9 @@ class DAQWorker(QObject):
|
||||
data = json.loads(response_data) if response_data else []
|
||||
if emit_status:
|
||||
# fetch status and bkg in parallel (simple sequential here)
|
||||
status_req = QNetworkRequest(QUrl(f"{self.__base_url}/fluorimeter/status"))
|
||||
status_req.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
status_reply = self.__net_manager.get(status_req)
|
||||
status_req = QNetworkRequest(QUrl(f"{self._base_url}/fluorimeter/status"))
|
||||
status_req.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
status_reply = self._net_manager.get(status_req)
|
||||
status_reply.finished.connect(
|
||||
lambda: self._handle_fluorimeter_status_and_emit(data, status_reply)
|
||||
)
|
||||
@@ -2086,9 +2086,9 @@ class DAQWorker(QObject):
|
||||
try:
|
||||
s_payload = self.handle_response(status_reply)
|
||||
s = int(s_payload) if s_payload not in ("", "null") else -1
|
||||
b_req = QNetworkRequest(QUrl(f"{self.__base_url}/fluorimeter/background"))
|
||||
b_req.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
b_reply = self.__net_manager.get(b_req)
|
||||
b_req = QNetworkRequest(QUrl(f"{self._base_url}/fluorimeter/background"))
|
||||
b_req.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
b_reply = self._net_manager.get(b_req)
|
||||
b_reply.finished.connect(lambda: self._emit_fluorimeter_with_bkg(data, s, b_reply))
|
||||
except Exception as e:
|
||||
logger.error(f"Fluorimeter status error: {e}")
|
||||
@@ -2107,13 +2107,13 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot()
|
||||
def fluorimeter_spectrum(self, f: FluorescenceSpectrumParameterModel):
|
||||
if self.__base_url is None:
|
||||
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 = 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 = 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):
|
||||
@@ -2127,20 +2127,20 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot()
|
||||
def fluorimeter_request_snapshot(self):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
return
|
||||
req = QNetworkRequest(QUrl(f"{self.__base_url}/fluorimeter/data"))
|
||||
req.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(req)
|
||||
req = QNetworkRequest(QUrl(f"{self._base_url}/fluorimeter/data"))
|
||||
req.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(req)
|
||||
reply.finished.connect(lambda: self._handle_fluorimeter_data(reply, emit_status=True))
|
||||
|
||||
# Optional: SSE listener for live updates
|
||||
def start_fluorimeter_stream(self):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
return
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/sse/fluorimeter"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/sse/fluorimeter"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(request)
|
||||
reply.readyRead.connect(lambda: self._read_fluorimeter_stream(reply))
|
||||
reply.finished.connect(lambda: reply.deleteLater())
|
||||
|
||||
@@ -2186,19 +2186,19 @@ class DAQWorker(QObject):
|
||||
|
||||
Server default is grouped. We flatten grouped payloads for existing UI.
|
||||
"""
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
self.error_codes_loaded.emit(export_error_codes())
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/meta/error-codes"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/meta/error-codes"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(request)
|
||||
reply.finished.connect(lambda: self._handle_error_codes_response(reply))
|
||||
|
||||
def _retry_error_codes_legacy(self) -> None:
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/meta/error-codes/flat"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/meta/error-codes/flat"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(request)
|
||||
reply.finished.connect(lambda: self._handle_error_codes_response(reply))
|
||||
|
||||
@Slot(QNetworkReply)
|
||||
@@ -2228,7 +2228,7 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot(str, str)
|
||||
def send_screenshot_db(self, filename: str = "", message: str = ""):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info(f"POST /samcam/send_screenshot_db?filename={filename}&message={message}")
|
||||
return
|
||||
|
||||
@@ -2248,22 +2248,22 @@ class DAQWorker(QObject):
|
||||
|
||||
def start_baton_stream(self):
|
||||
"""Start SSE stream for baton status updates."""
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
return
|
||||
|
||||
if self._baton_stream_reply is not None:
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/sse/baton"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/sse/baton"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(request)
|
||||
reply.readyRead.connect(lambda: self._read_baton_stream(reply))
|
||||
reply.finished.connect(self._restart_baton_stream)
|
||||
self._baton_stream_reply = reply
|
||||
|
||||
def _restart_baton_stream(self):
|
||||
self._baton_stream_reply = None
|
||||
if self.__base_url is not None:
|
||||
if self._base_url is not None:
|
||||
QTimer.singleShot(1000, self.start_baton_stream)
|
||||
|
||||
def _read_baton_stream(self, reply: QNetworkReply):
|
||||
@@ -2307,14 +2307,14 @@ class DAQWorker(QObject):
|
||||
"""
|
||||
Request the baton to gain write access to the beamline.
|
||||
"""
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info("POST /baton/request")
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/baton/request"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/baton/request"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
reply = self.__net_manager.post(request, QByteArray(b""))
|
||||
reply = self._net_manager.post(request, QByteArray(b""))
|
||||
reply.finished.connect(lambda: self._handle_baton_request_response(reply))
|
||||
|
||||
def _handle_baton_request_response(self, reply: QNetworkReply):
|
||||
@@ -2346,16 +2346,16 @@ class DAQWorker(QObject):
|
||||
Args:
|
||||
accept: True to grant the baton, False to refuse.
|
||||
"""
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info(f"POST /baton/respond?accept={accept}")
|
||||
return
|
||||
|
||||
request = QNetworkRequest(
|
||||
QUrl(f"{self.__base_url}/baton/respond?accept={str(accept).lower()}")
|
||||
QUrl(f"{self._base_url}/baton/respond?accept={str(accept).lower()}")
|
||||
)
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
reply = self.__net_manager.post(request, QByteArray(b""))
|
||||
reply = self._net_manager.post(request, QByteArray(b""))
|
||||
reply.finished.connect(lambda: self._handle_baton_response_result(reply))
|
||||
|
||||
def _handle_baton_response_result(self, reply: QNetworkReply):
|
||||
@@ -2386,12 +2386,12 @@ class DAQWorker(QObject):
|
||||
@Slot()
|
||||
def check_baton_timeout(self):
|
||||
"""Poll to check if timeout has been reached."""
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/baton/check_timeout"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/baton/check_timeout"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(request)
|
||||
reply.finished.connect(lambda: self._handle_baton_timeout_response(reply))
|
||||
|
||||
def _handle_baton_timeout_response(self, reply: QNetworkReply):
|
||||
@@ -2448,18 +2448,18 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot()
|
||||
def load_gui_sessions(self):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
self.gui_sessions_loaded.emit([])
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/admin/gui_sessions"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/admin/gui_sessions"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(request)
|
||||
reply.finished.connect(lambda: self._handle_gui_sessions_response(reply))
|
||||
|
||||
@Slot(int, int)
|
||||
def request_gui_close(self, session_id: int, grace_seconds: int = 60):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info(
|
||||
f"POST /admin/gui_sessions/{session_id}/request_close?grace_seconds={grace_seconds}"
|
||||
)
|
||||
@@ -2467,23 +2467,23 @@ class DAQWorker(QObject):
|
||||
|
||||
request = QNetworkRequest(
|
||||
QUrl(
|
||||
f"{self.__base_url}/admin/gui_sessions/{session_id}/request_close?grace_seconds={grace_seconds}"
|
||||
f"{self._base_url}/admin/gui_sessions/{session_id}/request_close?grace_seconds={grace_seconds}"
|
||||
)
|
||||
)
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
reply = self.__net_manager.post(request, QByteArray(b""))
|
||||
reply = self._net_manager.post(request, QByteArray(b""))
|
||||
reply.finished.connect(lambda: self._handle_gui_session_mutation_response(reply))
|
||||
|
||||
@Slot(int)
|
||||
def force_remove_gui_session(self, session_id: int):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info(f"DELETE /admin/gui_sessions/{session_id}")
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/admin/gui_sessions/{session_id}"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.deleteResource(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/admin/gui_sessions/{session_id}"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.deleteResource(request)
|
||||
reply.finished.connect(lambda: self._handle_gui_session_mutation_response(reply))
|
||||
|
||||
def _handle_gui_session_mutation_response(self, reply: QNetworkReply):
|
||||
@@ -2502,15 +2502,15 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot(int)
|
||||
def report_gui_interaction(self, session_id: int):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
return
|
||||
|
||||
request = QNetworkRequest(
|
||||
QUrl(f"{self.__base_url}/admin/gui_sessions/{session_id}/interaction")
|
||||
QUrl(f"{self._base_url}/admin/gui_sessions/{session_id}/interaction")
|
||||
)
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
reply = self.__net_manager.post(request, QByteArray(b""))
|
||||
reply = self._net_manager.post(request, QByteArray(b""))
|
||||
reply.finished.connect(lambda: reply.deleteLater())
|
||||
|
||||
def cleanup(self) -> None:
|
||||
@@ -2525,8 +2525,8 @@ class DAQWorker(QObject):
|
||||
logger.warning(f"Failed to stop _baton_timeout_timer: {e}")
|
||||
|
||||
try:
|
||||
if hasattr(self, "_DAQWorker__timer") and self.__timer is not None:
|
||||
self.__timer.stop()
|
||||
if hasattr(self, "_timer") and self._timer is not None:
|
||||
self._timer.stop()
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to stop __timer: {e}")
|
||||
|
||||
|
||||
@@ -7,20 +7,20 @@ class JFJochDBusClient(QObject):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.__session_bus = QDBusConnection.sessionBus()
|
||||
self.__interface = None
|
||||
self.__dbus_available = False
|
||||
self._session_bus = QDBusConnection.sessionBus()
|
||||
self._interface = None
|
||||
self._dbus_available = False
|
||||
|
||||
self.__beamline = mx_beamline()
|
||||
self._beamline = mx_beamline()
|
||||
try:
|
||||
self.__detector_url = get_jfjoch_url(self.__beamline)
|
||||
self._detector_url = get_jfjoch_url(self._beamline)
|
||||
except ValueError:
|
||||
self.__detector_url = None
|
||||
self._detector_url = None
|
||||
|
||||
try:
|
||||
self.__session_bus = QDBusConnection.sessionBus()
|
||||
if self.__session_bus.isConnected():
|
||||
self.__dbus_available = True
|
||||
self._session_bus = QDBusConnection.sessionBus()
|
||||
if self._session_bus.isConnected():
|
||||
self._dbus_available = True
|
||||
print("D-Bus session bus connected")
|
||||
else:
|
||||
print("D-Bus not available")
|
||||
@@ -30,23 +30,23 @@ class JFJochDBusClient(QObject):
|
||||
|
||||
def _ensure_interface(self):
|
||||
"""Ensure we have a valid interface, creating one if needed."""
|
||||
if not self.__dbus_available:
|
||||
if not self._dbus_available:
|
||||
print("D-Bus not available, cannot connect to JFJoch viewer")
|
||||
return False
|
||||
|
||||
if self.__interface is None or not self.__interface.isValid():
|
||||
if not self.__session_bus.isConnected():
|
||||
if self._interface is None or not self._interface.isValid():
|
||||
if not self._session_bus.isConnected():
|
||||
print("Session bus not connected")
|
||||
return False
|
||||
|
||||
self.__interface = QDBusInterface(
|
||||
self._interface = QDBusInterface(
|
||||
"ch.psi.jfjoch_viewer", # Service name
|
||||
"/", # Object path
|
||||
"ch.psi.jfjoch_viewer", # Interface name
|
||||
self.__session_bus,
|
||||
self._session_bus,
|
||||
)
|
||||
|
||||
if not self.__interface.isValid():
|
||||
if not self._interface.isValid():
|
||||
print("Service 'ch.psi.jfjoch_viewer' not available")
|
||||
return False
|
||||
|
||||
@@ -54,7 +54,7 @@ class JFJochDBusClient(QObject):
|
||||
|
||||
@Slot(str, int)
|
||||
def load_image(self, name: str, number: int):
|
||||
if not self.__dbus_available:
|
||||
if not self._dbus_available:
|
||||
print("D-Bus not available, cannot load image in JFJoch viewer")
|
||||
return
|
||||
|
||||
@@ -64,13 +64,13 @@ class JFJochDBusClient(QObject):
|
||||
path = "/sls/mx/data/" + name + "_master.h5"
|
||||
print(f"Load dataset: {path} image: {number}")
|
||||
if self._ensure_interface():
|
||||
self.__interface.call("LoadFile", path, number, 1)
|
||||
self._interface.call("LoadFile", path, number, 1)
|
||||
|
||||
@Slot()
|
||||
def load_online(self):
|
||||
if not self.__dbus_available:
|
||||
if not self._dbus_available:
|
||||
print("D-Bus not available, cannot load online data in JFJoch viewer")
|
||||
return
|
||||
|
||||
if self._ensure_interface():
|
||||
self.__interface.call("LoadFile", self.__detector_url, -1, 1)
|
||||
self._interface.call("LoadFile", self._detector_url, -1, 1)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,9 +15,9 @@ class LoginDialog(QDialog):
|
||||
self.setWindowTitle("User Authentication")
|
||||
self.setMinimumWidth(400)
|
||||
self.setStyleSheet("background-color: rgb(216, 228, 253);")
|
||||
self.__base_url = base_url
|
||||
self.__reply = None
|
||||
self.__network_manager = None
|
||||
self._base_url = base_url
|
||||
self._reply = None
|
||||
self._network_manager = None
|
||||
|
||||
# Layout
|
||||
layout = QVBoxLayout(self)
|
||||
@@ -44,14 +44,14 @@ class LoginDialog(QDialog):
|
||||
|
||||
@Slot()
|
||||
def authenticate(self):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
token_data = TokenData(
|
||||
sub=self.name_entry.text(), staff=True, session=15, pgroups=["p16371", "p22233"]
|
||||
)
|
||||
self.token = jwt.encode(token_data.model_dump(), "ABC123")
|
||||
self.accept()
|
||||
self.__network_manager = QNetworkAccessManager(self)
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/token"))
|
||||
self._network_manager = QNetworkAccessManager(self)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/token"))
|
||||
request.setHeader(
|
||||
QNetworkRequest.KnownHeaders.ContentTypeHeader, "application/x-www-form-urlencoded"
|
||||
)
|
||||
@@ -60,17 +60,15 @@ class LoginDialog(QDialog):
|
||||
payload.addQueryItem("username", f"{self.name_entry.text()}")
|
||||
payload.addQueryItem("password", "")
|
||||
payload_string = payload.toString()
|
||||
self.__reply = self.__network_manager.post(
|
||||
request, QByteArray.fromStdString(payload_string)
|
||||
)
|
||||
self.__reply.finished.connect(self.handle_token_response)
|
||||
self._reply = self._network_manager.post(request, QByteArray.fromStdString(payload_string))
|
||||
self._reply.finished.connect(self.handle_token_response)
|
||||
|
||||
@Slot()
|
||||
def handle_token_response(self):
|
||||
if self.__reply is not None and self.__reply.error() == QNetworkReply.NetworkError.NoError:
|
||||
response_data = self.__reply.readAll().data()
|
||||
if self._reply is not None and self._reply.error() == QNetworkReply.NetworkError.NoError:
|
||||
response_data = self._reply.readAll().data()
|
||||
response_json = json.loads(response_data.decode("utf-8"))
|
||||
if "access_token" in response_json:
|
||||
self.token = response_json["access_token"]
|
||||
self.__reply.deleteLater()
|
||||
self._reply.deleteLater()
|
||||
self.accept()
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
from PySide6.QtCore import Signal, Slot
|
||||
from PySide6.QtWidgets import (
|
||||
QTableWidget,
|
||||
QHBoxLayout,
|
||||
QHeaderView,
|
||||
QPushButton,
|
||||
QTableWidget,
|
||||
QTableWidgetItem,
|
||||
QWidget,
|
||||
QHBoxLayout,
|
||||
QPushButton,
|
||||
)
|
||||
|
||||
from aare.gui.scan_logic.raster_grid_manager import RasterGridManager
|
||||
@@ -19,7 +19,7 @@ class RasterGridTable(QTableWidget):
|
||||
|
||||
def __init__(self, raster_mgr: RasterGridManager, parent=None):
|
||||
super().__init__(parent)
|
||||
self.__raster_mgr = raster_mgr
|
||||
self._raster_mgr = raster_mgr
|
||||
|
||||
self.setColumnCount(5)
|
||||
self.setHorizontalHeaderLabels(["Omega", "Chi", "Phi", "Size (WxH)", "Actions"])
|
||||
@@ -36,14 +36,14 @@ class RasterGridTable(QTableWidget):
|
||||
self.setMinimumHeight(100)
|
||||
|
||||
# Connect to raster manager signals
|
||||
self.__raster_mgr.completed_grid_updated.connect(self.refresh_table)
|
||||
self.copy_clicked.connect(self.__raster_mgr.completed_grid_redo)
|
||||
self.delete_clicked.connect(self.__raster_mgr.completed_grid_scan_delete)
|
||||
self.visible_clicked.connect(self.__raster_mgr.completed_grid_scan_toggle_visible)
|
||||
self.goto_clicked.connect(self.__raster_mgr.completed_grid_scan_goto)
|
||||
self._raster_mgr.completed_grid_updated.connect(self.refresh_table)
|
||||
self.copy_clicked.connect(self._raster_mgr.completed_grid_redo)
|
||||
self.delete_clicked.connect(self._raster_mgr.completed_grid_scan_delete)
|
||||
self.visible_clicked.connect(self._raster_mgr.completed_grid_scan_toggle_visible)
|
||||
self.goto_clicked.connect(self._raster_mgr.completed_grid_scan_goto)
|
||||
|
||||
def add_rows(self):
|
||||
for r in self.__raster_mgr.get_completed_grids():
|
||||
for r in self._raster_mgr.get_completed_grids():
|
||||
req = r.request
|
||||
|
||||
row_position = self.rowCount()
|
||||
|
||||
@@ -40,10 +40,10 @@ class StatusBar(QStatusBar):
|
||||
|
||||
def __init__(self, token: TokenData, parent=None):
|
||||
super().__init__(parent)
|
||||
self.__decoded_token = token
|
||||
self.__status = None
|
||||
self.__is_staff = self.__decoded_token.staff
|
||||
self.__allowed_pgroups = self.__decoded_token.pgroups
|
||||
self._decoded_token = token
|
||||
self._status = None
|
||||
self._is_staff = self._decoded_token.staff
|
||||
self._allowed_pgroups = self._decoded_token.pgroups
|
||||
|
||||
self._message_clear_timer = QTimer(self)
|
||||
self._message_clear_timer.setSingleShot(True)
|
||||
@@ -134,7 +134,7 @@ class StatusBar(QStatusBar):
|
||||
@Slot(DAQStatusModel)
|
||||
def update_daq_status(self, status: DAQStatusModel):
|
||||
try:
|
||||
self.__status = status
|
||||
self._status = status
|
||||
if status.bl.flux_ph_s is None:
|
||||
self.flux.set_value("0")
|
||||
else:
|
||||
@@ -300,10 +300,10 @@ class StatusBar(QStatusBar):
|
||||
|
||||
def _update_session_display(self):
|
||||
"""Update session label based on current status."""
|
||||
if self.__status is None:
|
||||
if self._status is None:
|
||||
return
|
||||
|
||||
session_state = self.__status.session.session
|
||||
session_state = self._status.session.session
|
||||
|
||||
# Base text
|
||||
if session_state == SessionsStateEnum.OwnedByYou:
|
||||
@@ -327,8 +327,8 @@ class StatusBar(QStatusBar):
|
||||
|
||||
def show_session_menu(self):
|
||||
menu = QMenu(self)
|
||||
is_busy = self.__status and self.__status.busy
|
||||
session_state = self.__status.session.session if self.__status else SessionsStateEnum.Vacant
|
||||
is_busy = self._status and self._status.busy
|
||||
session_state = self._status.session.session if self._status else SessionsStateEnum.Vacant
|
||||
# Determine if we are the holder or waiting for baton
|
||||
is_yours = session_state in (
|
||||
SessionsStateEnum.OwnedByYou,
|
||||
@@ -362,7 +362,7 @@ class StatusBar(QStatusBar):
|
||||
# Already have a pending request - show cancel option
|
||||
action_cancel = menu.addAction("Cancel Request")
|
||||
action_cancel.triggered.connect(self._on_cancel_request_clicked)
|
||||
elif self.__is_staff:
|
||||
elif self._is_staff:
|
||||
# Staff can always grab (override)
|
||||
action_grab = menu.addAction("Grab (Override)")
|
||||
action_grab.setEnabled(not is_busy) # Still respect busy for safety
|
||||
@@ -395,11 +395,11 @@ class StatusBar(QStatusBar):
|
||||
|
||||
# --- END SESSION (cleanup) ---
|
||||
action_end = menu.addAction("End Session")
|
||||
action_end.setEnabled(bool(is_yours or self.__is_staff))
|
||||
action_end.setEnabled(bool(is_yours or self._is_staff))
|
||||
action_end.triggered.connect(self.end_session_clicked)
|
||||
|
||||
# --- STAFF: FORCE GRAB (emergency) ---
|
||||
if self.__is_staff and is_other:
|
||||
if self._is_staff and is_other:
|
||||
menu.addSeparator()
|
||||
action_force = menu.addAction("⚠️ Force Take Over")
|
||||
action_force.triggered.connect(self._on_force_session_clicked)
|
||||
@@ -411,18 +411,16 @@ class StatusBar(QStatusBar):
|
||||
menu.exec()
|
||||
|
||||
def show_pgroup_menu(self):
|
||||
in_curr = self.__status
|
||||
in_curr = self._status
|
||||
|
||||
logger.info(f"in_curr is {in_curr}")
|
||||
|
||||
if not in_curr:
|
||||
print(f"is status None?: {self.__status}")
|
||||
print(f"is status None?: {self._status}")
|
||||
|
||||
allow_pgroup_menu = self.__is_staff or in_curr
|
||||
allow_pgroup_menu = self._is_staff or in_curr
|
||||
|
||||
owned_by_you = (
|
||||
self.__status and self.__status.session.session == SessionsStateEnum.OwnedByYou
|
||||
)
|
||||
owned_by_you = self._status and self._status.session.session == SessionsStateEnum.OwnedByYou
|
||||
|
||||
if allow_pgroup_menu:
|
||||
logger.info("showing pgroup menu")
|
||||
@@ -455,13 +453,13 @@ class StatusBar(QStatusBar):
|
||||
menu.exec()
|
||||
|
||||
def show_state_menu(self):
|
||||
if self.__status is None:
|
||||
if self._status is None:
|
||||
return
|
||||
|
||||
logger.info(self.__status.state)
|
||||
logger.info(self._status.state)
|
||||
menu = QMenu(self)
|
||||
|
||||
if self.__status.busy or self.__status.state in [BeamlineStateEnum.Moving]:
|
||||
if self._status.busy or self._status.state in [BeamlineStateEnum.Moving]:
|
||||
return
|
||||
|
||||
action_0 = menu.addAction("Available transitions")
|
||||
@@ -472,7 +470,7 @@ class StatusBar(QStatusBar):
|
||||
action_0.setEnabled(False)
|
||||
menu.addSeparator()
|
||||
|
||||
if self.__status.state in [
|
||||
if self._status.state in [
|
||||
BeamlineStateEnum.RobotSampleExchange,
|
||||
BeamlineStateEnum.SampleExchange,
|
||||
BeamlineStateEnum.DewarTransfer,
|
||||
@@ -483,14 +481,14 @@ class StatusBar(QStatusBar):
|
||||
]:
|
||||
action_1 = menu.addAction("Sample alignment")
|
||||
action_1.triggered.connect(self.sa)
|
||||
elif self.__status.state in [BeamlineStateEnum.SampleAlignment]:
|
||||
elif self._status.state in [BeamlineStateEnum.SampleAlignment]:
|
||||
action_2 = menu.addAction("Manual sample exchange")
|
||||
action_2.triggered.connect(self.se)
|
||||
action_3 = menu.addAction("Dewar transfer")
|
||||
action_3.triggered.connect(self.dl)
|
||||
action_4 = menu.addAction("Beam location")
|
||||
action_4.triggered.connect(self.beam_location)
|
||||
elif self.__status.state in [BeamlineStateEnum.Maintenance]:
|
||||
elif self._status.state in [BeamlineStateEnum.Maintenance]:
|
||||
action_2 = menu.addAction("Manual sample exchange")
|
||||
action_2.triggered.connect(self.se)
|
||||
|
||||
@@ -520,12 +518,12 @@ class StatusBar(QStatusBar):
|
||||
self._show_pgroup_after_baton_grant()
|
||||
|
||||
def show_change_dialog(self):
|
||||
logger.debug(self.__decoded_token.pgroups)
|
||||
curr = self.__status.session.current_pgroup
|
||||
logger.debug(self._decoded_token.pgroups)
|
||||
curr = self._status.session.current_pgroup
|
||||
pgroups = [
|
||||
str(p) for p in (self.__allowed_pgroups or []) if p is not None and str(p).strip()
|
||||
str(p) for p in (self._allowed_pgroups or []) if p is not None and str(p).strip()
|
||||
]
|
||||
if self.__is_staff:
|
||||
if self._is_staff:
|
||||
|
||||
def _on_loaded(lst: list):
|
||||
try:
|
||||
@@ -543,7 +541,7 @@ class StatusBar(QStatusBar):
|
||||
pass
|
||||
|
||||
self.staff_pgroups_loaded.connect(_on_loaded)
|
||||
self.__list_staff_pgroups()
|
||||
self._list_staff_pgroups()
|
||||
return
|
||||
|
||||
self._generate_pgroup_dialogue(curr=curr, pgroups=pgroups)
|
||||
@@ -586,7 +584,7 @@ class StatusBar(QStatusBar):
|
||||
def open_shutter_clicked(self):
|
||||
self.open_shutter.emit()
|
||||
|
||||
def __list_staff_pgroups(self):
|
||||
def _list_staff_pgroups(self):
|
||||
self.get_all_pgroups.emit()
|
||||
return
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from PySide6.QtCore import Signal, Qt
|
||||
from PySide6.QtCore import Qt, Signal
|
||||
from PySide6.QtWidgets import QLabel
|
||||
|
||||
|
||||
@@ -7,16 +7,16 @@ class ValueLabel(QLabel):
|
||||
|
||||
def __init__(self, text: str, unit: str = "", parent=None):
|
||||
super().__init__(parent)
|
||||
self.__descr = text
|
||||
self.__unit = unit
|
||||
self._descr = text
|
||||
self._unit = unit
|
||||
|
||||
def set_value(self, s: str, color: str | None = None):
|
||||
if color is not None:
|
||||
self.setText(
|
||||
f"""{self.__descr}: <span style="color: {color};"><b>{s}</b> {self.__unit} </span>"""
|
||||
f"""{self._descr}: <span style="color: {color};"><b>{s}</b> {self._unit} </span>"""
|
||||
)
|
||||
else:
|
||||
self.setText(f"{self.__descr}: <b>{s}</b> {self.__unit}")
|
||||
self.setText(f"{self._descr}: <b>{s}</b> {self._unit}")
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
if event.button() == Qt.MouseButton.LeftButton:
|
||||
|
||||
@@ -15,9 +15,9 @@ from aare.daq.daq import AareDAQ
|
||||
|
||||
def test_execute_loop_centering_rejects_invalid_sample():
|
||||
daq = object.__new__(AareDAQ)
|
||||
daq._AareDAQ__set_state = MagicMock()
|
||||
daq._AareDAQ__devs = MagicMock()
|
||||
daq._AareDAQ__aare = MagicMock()
|
||||
daq._set_state = MagicMock()
|
||||
daq._devs = MagicMock()
|
||||
daq._aare = MagicMock()
|
||||
daq._handle_operation_error = MagicMock()
|
||||
daq._create_loop_centering_service = MagicMock()
|
||||
|
||||
@@ -29,9 +29,9 @@ def test_execute_loop_centering_rejects_invalid_sample():
|
||||
|
||||
def test_execute_loop_centering_handles_service_failure():
|
||||
daq = object.__new__(AareDAQ)
|
||||
daq._AareDAQ__set_state = MagicMock()
|
||||
daq._AareDAQ__devs = types.SimpleNamespace(lamp_light=None)
|
||||
daq._AareDAQ__aare = MagicMock()
|
||||
daq._set_state = MagicMock()
|
||||
daq._devs = types.SimpleNamespace(lamp_light=None)
|
||||
daq._aare = MagicMock()
|
||||
daq._handle_operation_error = MagicMock()
|
||||
daq.save_screenshot_db = MagicMock()
|
||||
|
||||
@@ -60,9 +60,9 @@ def test_execute_loop_centering_handles_service_failure():
|
||||
|
||||
def test_execute_loop_centering_handles_service_success():
|
||||
daq = object.__new__(AareDAQ)
|
||||
daq._AareDAQ__set_state = MagicMock()
|
||||
daq._AareDAQ__devs = MagicMock()
|
||||
daq._AareDAQ__aare = MagicMock()
|
||||
daq._set_state = MagicMock()
|
||||
daq._devs = MagicMock()
|
||||
daq._aare = MagicMock()
|
||||
daq._handle_operation_error = MagicMock()
|
||||
daq.save_screenshot_db = MagicMock()
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ def geom_model():
|
||||
def test_aare_wrapper_init(mock_grid, mock_proc, mock_sample, mock_tell, mock_api, mock_bl):
|
||||
wrapper = AareWrapper(bl=mock_bl)
|
||||
mock_api.assert_called_once()
|
||||
assert wrapper._AareWrapper__bl == mock_bl
|
||||
assert wrapper._bl == mock_bl
|
||||
|
||||
|
||||
@patch("aareDB.ApiClient")
|
||||
|
||||
@@ -49,7 +49,7 @@ def staff_token_data():
|
||||
|
||||
|
||||
def test_create_access_token(token_data):
|
||||
with patch("aare.daq.auth.SECRET_KEY", "test_secret"):
|
||||
with patch("aare.daq.auth.jwt_key", return_value="test_secret"):
|
||||
token = create_access_token(token_data)
|
||||
assert isinstance(token, str)
|
||||
payload = jwt.decode(token, "test_secret", algorithms=["HS256"])
|
||||
@@ -62,7 +62,7 @@ def test_authenticate_user(mock_cfg):
|
||||
patch("pwd.getpwnam") as mock_pwd,
|
||||
patch("os.getgrouplist") as mock_groups,
|
||||
patch("grp.getgrgid") as mock_grp,
|
||||
patch("aare.daq.auth.SECRET_KEY", "test_secret"),
|
||||
patch("aare.daq.auth.jwt_key", return_value="test_secret"),
|
||||
):
|
||||
mock_pwd.return_value.pw_name = "testuser"
|
||||
mock_pwd.return_value.pw_gid = 1000
|
||||
@@ -88,7 +88,7 @@ def test_authenticate_user(mock_cfg):
|
||||
|
||||
|
||||
def test_parse_token():
|
||||
with patch("aare.daq.auth.SECRET_KEY", "test_secret"):
|
||||
with patch("aare.daq.auth.jwt_key", return_value="test_secret"):
|
||||
token = jwt.encode(
|
||||
{"sub": "user", "pgroups": [], "session": 1, "staff": False}, "test_secret"
|
||||
)
|
||||
@@ -97,8 +97,9 @@ def test_parse_token():
|
||||
|
||||
|
||||
def test_parse_token_invalid():
|
||||
with pytest.raises(AuthenticationException):
|
||||
parse_token("invalid.token.here")
|
||||
with patch("aare.daq.auth.jwt_key", return_value="test_secret"):
|
||||
with pytest.raises(AuthenticationException):
|
||||
parse_token("invalid.token.here")
|
||||
|
||||
|
||||
def test_check_jwt_ro(mock_cfg, token_data):
|
||||
|
||||
@@ -31,8 +31,8 @@ class _FakeRedis:
|
||||
|
||||
def _make_config_with_fake_redis() -> BeamlineConfig:
|
||||
cfg = BeamlineConfig.__new__(BeamlineConfig)
|
||||
cfg._BeamlineConfig__bl = "testbeamline"
|
||||
cfg._BeamlineConfig__client = _FakeRedis()
|
||||
cfg._bl = "testbeamline"
|
||||
cfg._client = _FakeRedis()
|
||||
return cfg
|
||||
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ def test_execute_face_detection_reports_failure(monkeypatch):
|
||||
|
||||
calls = {"set_state": [], "handle_error": []}
|
||||
|
||||
monkeypatch.setattr(daq, "_AareDAQ__set_state", lambda state: calls["set_state"].append(state))
|
||||
monkeypatch.setattr(daq, "_set_state", lambda state: calls["set_state"].append(state))
|
||||
monkeypatch.setattr(
|
||||
daq,
|
||||
"_create_face_detection_service",
|
||||
@@ -49,7 +49,7 @@ def test_execute_face_detection_can_skip_error_reporting(monkeypatch):
|
||||
|
||||
handle_error_calls = []
|
||||
|
||||
monkeypatch.setattr(daq, "_AareDAQ__set_state", lambda state: None)
|
||||
monkeypatch.setattr(daq, "_set_state", lambda state: None)
|
||||
monkeypatch.setattr(
|
||||
daq,
|
||||
"_create_face_detection_service",
|
||||
@@ -77,7 +77,7 @@ def test_public_face_detection_uses_execute_face_detection(monkeypatch):
|
||||
|
||||
daq = object.__new__(AareDAQ)
|
||||
cfg = types.SimpleNamespace(try_set_busy=lambda timeout=360: None, state_busy=False)
|
||||
setattr(daq, "_AareDAQ__cfg", cfg)
|
||||
setattr(daq, "_cfg", cfg)
|
||||
|
||||
setattr(
|
||||
daq,
|
||||
|
||||
@@ -25,7 +25,7 @@ def mlbox(mock_wrapper):
|
||||
|
||||
def test_init_x10sa(mock_wrapper):
|
||||
ml = MlBox(MXBeamline.X10SA, wrapper=mock_wrapper)
|
||||
assert ml._MlBox__beamline == MXBeamline.X10SA
|
||||
assert ml._beamline == MXBeamline.X10SA
|
||||
|
||||
|
||||
def test_predict_uses_injected_wrapper():
|
||||
|
||||
@@ -29,10 +29,10 @@ def _make_sample(sample_id: int, name: str) -> SampleShortInfo:
|
||||
|
||||
def _make_daq(previous_sample: SampleShortInfo | None) -> AareDAQ:
|
||||
daq = object.__new__(AareDAQ)
|
||||
daq._AareDAQ__cfg = SimpleNamespace(current_sample=previous_sample)
|
||||
daq._AareDAQ__aare = MagicMock()
|
||||
daq._AareDAQ__devs = MagicMock()
|
||||
daq._AareDAQ__set_state = MagicMock()
|
||||
daq._cfg = SimpleNamespace(current_sample=previous_sample)
|
||||
daq._aare = MagicMock()
|
||||
daq._devs = MagicMock()
|
||||
daq._set_state = MagicMock()
|
||||
daq._handle_operation_error = MagicMock()
|
||||
daq.save_screenshot_db = MagicMock()
|
||||
daq.sync_current_sample_from_tell = MagicMock(return_value=previous_sample)
|
||||
@@ -43,8 +43,8 @@ def _make_daq(previous_sample: SampleShortInfo | None) -> AareDAQ:
|
||||
@pytest.fixture
|
||||
def bare_daq():
|
||||
daq = object.__new__(AareDAQ)
|
||||
daq._AareDAQ__cfg = SimpleNamespace()
|
||||
daq._AareDAQ__devs = SimpleNamespace()
|
||||
daq._cfg = SimpleNamespace()
|
||||
daq._devs = SimpleNamespace()
|
||||
return daq
|
||||
|
||||
|
||||
@@ -52,8 +52,8 @@ def test_create_mounting_service_builds_expected_context(mock_logger, bare_daq):
|
||||
service = bare_daq._create_mounting_service()
|
||||
|
||||
assert isinstance(service.ctx, MountingContext)
|
||||
assert service.ctx.deps.cfg is bare_daq._AareDAQ__cfg
|
||||
assert service.ctx.deps.devs is bare_daq._AareDAQ__devs
|
||||
assert service.ctx.deps.cfg is bare_daq._cfg
|
||||
assert service.ctx.deps.devs is bare_daq._devs
|
||||
assert service.ctx.settings.mount_position == ABR_POS_MOUNT
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ def test_execute_mount_and_prepare_success_uses_mounting_result_fields():
|
||||
|
||||
assert result is True
|
||||
|
||||
send_calls = daq._AareDAQ__aare.send_sample_event.call_args_list
|
||||
send_calls = daq._aare.send_sample_event.call_args_list
|
||||
assert send_calls[0].args[0] == previous_sample.db_id
|
||||
assert send_calls[0].args[1] == SampleEventType.UNMOUNTING
|
||||
assert send_calls[1].args[0] == target_sample.db_id
|
||||
@@ -108,10 +108,8 @@ def test_execute_mount_and_prepare_success_uses_mounting_result_fields():
|
||||
daq.save_screenshot_db.assert_called_once_with(
|
||||
target_sample.db_id, f"{target_sample.db_id}_mounted"
|
||||
)
|
||||
assert (
|
||||
daq._AareDAQ__set_state.call_args_list[0].args[0] == BeamlineStateEnum.RobotSampleExchange
|
||||
)
|
||||
assert daq._AareDAQ__set_state.call_args_list[-1].args[0] == BeamlineStateEnum.SampleAlignment
|
||||
assert daq._set_state.call_args_list[0].args[0] == BeamlineStateEnum.RobotSampleExchange
|
||||
assert daq._set_state.call_args_list[-1].args[0] == BeamlineStateEnum.SampleAlignment
|
||||
|
||||
|
||||
def test_execute_mount_and_prepare_marks_previous_sample_unmounted_when_mount_fails_after_auto_unmount():
|
||||
@@ -132,9 +130,9 @@ def test_execute_mount_and_prepare_marks_previous_sample_unmounted_when_mount_fa
|
||||
result = daq._execute_mount_and_prepare(target_sample)
|
||||
|
||||
assert result is False
|
||||
assert daq._AareDAQ__cfg.current_sample is None
|
||||
assert daq._cfg.current_sample is None
|
||||
|
||||
send_calls = daq._AareDAQ__aare.send_sample_event.call_args_list
|
||||
send_calls = daq._aare.send_sample_event.call_args_list
|
||||
assert send_calls[0].args[0] == previous_sample.db_id
|
||||
assert send_calls[0].args[1] == SampleEventType.UNMOUNTING
|
||||
|
||||
@@ -150,7 +148,7 @@ def test_execute_mount_and_prepare_marks_previous_sample_unmounted_when_mount_fa
|
||||
assert daq._handle_operation_error.call_args.kwargs["sample"] == target_sample
|
||||
assert daq._handle_operation_error.call_args.kwargs["event_type"] == SampleEventType.MOUNTFAILED
|
||||
|
||||
assert daq._AareDAQ__set_state.call_args_list[-1].args[0] == BeamlineStateEnum.SampleAlignment
|
||||
assert daq._set_state.call_args_list[-1].args[0] == BeamlineStateEnum.SampleAlignment
|
||||
|
||||
|
||||
def test_execute_mount_and_prepare_does_not_mark_previous_sample_unmounted_when_not_confirmed():
|
||||
@@ -171,9 +169,9 @@ def test_execute_mount_and_prepare_does_not_mark_previous_sample_unmounted_when_
|
||||
result = daq._execute_mount_and_prepare(target_sample)
|
||||
|
||||
assert result is False
|
||||
assert daq._AareDAQ__cfg.current_sample == previous_sample
|
||||
assert daq._cfg.current_sample == previous_sample
|
||||
|
||||
send_calls = daq._AareDAQ__aare.send_sample_event.call_args_list
|
||||
send_calls = daq._aare.send_sample_event.call_args_list
|
||||
assert len(send_calls) == 2
|
||||
assert send_calls[0].args[0] == previous_sample.db_id
|
||||
assert send_calls[0].args[1] == SampleEventType.UNMOUNTING
|
||||
@@ -201,7 +199,7 @@ def test_execute_mount_and_prepare_unmount_success_uses_unmount_operation():
|
||||
|
||||
assert result is True
|
||||
|
||||
send_calls = daq._AareDAQ__aare.send_sample_event.call_args_list
|
||||
send_calls = daq._aare.send_sample_event.call_args_list
|
||||
assert len(send_calls) == 2
|
||||
assert send_calls[0].args[0] == previous_sample.db_id
|
||||
assert send_calls[0].args[1] == SampleEventType.UNMOUNTING
|
||||
@@ -209,10 +207,8 @@ def test_execute_mount_and_prepare_unmount_success_uses_unmount_operation():
|
||||
assert send_calls[1].args[1] == SampleEventType.UNMOUNTED
|
||||
|
||||
daq.save_screenshot_db.assert_not_called()
|
||||
assert (
|
||||
daq._AareDAQ__set_state.call_args_list[0].args[0] == BeamlineStateEnum.RobotSampleExchange
|
||||
)
|
||||
assert daq._AareDAQ__set_state.call_args_list[-1].args[0] == BeamlineStateEnum.SampleAlignment
|
||||
assert daq._set_state.call_args_list[0].args[0] == BeamlineStateEnum.RobotSampleExchange
|
||||
assert daq._set_state.call_args_list[-1].args[0] == BeamlineStateEnum.SampleAlignment
|
||||
|
||||
|
||||
def test_raise_if_critical_jfjoch_detector_error_preserves_jfjoch_exception_family():
|
||||
@@ -279,9 +275,9 @@ def test_raise_if_critical_jfjoch_detector_error_ignores_non_critical_jfjoch_err
|
||||
|
||||
def test_create_loop_centering_service_uses_shared_screenshot_service():
|
||||
daq = object.__new__(AareDAQ)
|
||||
daq._AareDAQ__cfg = SimpleNamespace()
|
||||
daq._AareDAQ__devs = SimpleNamespace()
|
||||
daq._AareDAQ__mlbox = SimpleNamespace(predict_all_best=MagicMock())
|
||||
daq._cfg = SimpleNamespace()
|
||||
daq._devs = SimpleNamespace()
|
||||
daq._mlbox = SimpleNamespace(predict_all_best=MagicMock())
|
||||
daq._screenshot_service = MagicMock(spec=ScreenshotService)
|
||||
daq._append_smargon_trace = MagicMock()
|
||||
|
||||
@@ -296,12 +292,12 @@ def test_create_loop_centering_service_uses_shared_screenshot_service():
|
||||
|
||||
def test_create_raster_service_uses_shared_screenshot_service():
|
||||
daq = object.__new__(AareDAQ)
|
||||
daq._AareDAQ__cfg = SimpleNamespace()
|
||||
daq._AareDAQ__devs = SimpleNamespace()
|
||||
daq._AareDAQ__mlbox = SimpleNamespace()
|
||||
daq._AareDAQ__jfjoch = SimpleNamespace()
|
||||
daq._AareDAQ__aare = SimpleNamespace()
|
||||
daq._AareDAQ__set_state = MagicMock()
|
||||
daq._cfg = SimpleNamespace()
|
||||
daq._devs = SimpleNamespace()
|
||||
daq._mlbox = SimpleNamespace()
|
||||
daq._jfjoch = SimpleNamespace()
|
||||
daq._aare = SimpleNamespace()
|
||||
daq._set_state = MagicMock()
|
||||
daq._screenshot_service = MagicMock(spec=ScreenshotService)
|
||||
|
||||
type(daq).sample = property(lambda self: None)
|
||||
|
||||
@@ -10,11 +10,11 @@ from aare.daq.spreadsheetupdater import get_ws_headers, on_message, set_spreadsh
|
||||
@pytest.fixture
|
||||
def mock_config():
|
||||
with patch("aare.daq.spreadsheetupdater.config") as mock:
|
||||
mock._BeamlineConfig__bl = "X10SA"
|
||||
mock._BeamlineConfig__client = MagicMock()
|
||||
mock._bl = "X10SA"
|
||||
mock._client = MagicMock()
|
||||
# Mocking private attributes access which the code uses
|
||||
mock._BeamlineConfig__client = mock._BeamlineConfig__client
|
||||
mock._BeamlineConfig__bl = mock._BeamlineConfig__bl
|
||||
mock._client = mock._client
|
||||
mock._bl = mock._bl
|
||||
yield mock
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ def test_set_spreadsheet_in_redis(mock_config):
|
||||
data = {"test": "data"}
|
||||
with patch("aare.daq.spreadsheetupdater.config") as mock_cfg_internal:
|
||||
mock_client = MagicMock()
|
||||
mock_cfg_internal._BeamlineConfig__client = mock_client
|
||||
mock_cfg_internal._client = mock_client
|
||||
mock_cfg_internal.client = mock_client
|
||||
|
||||
set_spreadsheet_in_redis(data)
|
||||
@@ -88,7 +88,7 @@ def test_on_message_success(mock_config):
|
||||
with patch("aare.daq.spreadsheetupdater.PuckWithTellPosition", side_effect=mock_pucks):
|
||||
on_message(None, message)
|
||||
|
||||
calls = mock_config._BeamlineConfig__client.set.call_args_list
|
||||
calls = mock_config._client.set.call_args_list
|
||||
written_keys = [call.args[0] for call in calls]
|
||||
|
||||
assert "X10SA:sample_spreadsheet" in written_keys
|
||||
@@ -119,9 +119,9 @@ def test_on_message_empty_ref(mock_config):
|
||||
on_message(None, message)
|
||||
|
||||
ref_key = "X10SA:reference-tools"
|
||||
mock_config._BeamlineConfig__client.delete.assert_called_with(ref_key)
|
||||
mock_config._client.delete.assert_called_with(ref_key)
|
||||
|
||||
|
||||
def test_on_message_invalid_json(mock_config):
|
||||
on_message(None, "invalid json")
|
||||
mock_config._BeamlineConfig__client.set.assert_not_called()
|
||||
mock_config._client.set.assert_not_called()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import json
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from aare.daq import tellupdater
|
||||
|
||||
|
||||
@@ -107,13 +108,13 @@ def test_record_tell_event_writes_history_to_redis():
|
||||
tellupdater.tell_event_history.clear()
|
||||
|
||||
mock_config = MagicMock()
|
||||
mock_config._BeamlineConfig__bl = "x10sa"
|
||||
mock_config._bl = "x10sa"
|
||||
|
||||
with patch("aare.daq.tellupdater.config", mock_config):
|
||||
tellupdater.record_tell_event("Motion Task", "dry")
|
||||
|
||||
mock_config._BeamlineConfig__client.set.assert_called_once()
|
||||
redis_key, redis_value = mock_config._BeamlineConfig__client.set.call_args.args
|
||||
mock_config._client.set.assert_called_once()
|
||||
redis_key, redis_value = mock_config._client.set.call_args.args
|
||||
assert redis_key == "x10sa:tell_events"
|
||||
|
||||
payload = json.loads(redis_value)
|
||||
@@ -128,13 +129,13 @@ def test_record_tell_event_keeps_last_25_events():
|
||||
tellupdater.tell_event_history.clear()
|
||||
|
||||
mock_config = MagicMock()
|
||||
mock_config._BeamlineConfig__bl = "x10sa"
|
||||
mock_config._bl = "x10sa"
|
||||
|
||||
with patch("aare.daq.tellupdater.config", mock_config):
|
||||
for idx in range(30):
|
||||
tellupdater.record_tell_event("Motion Sync", f"event-{idx}")
|
||||
|
||||
redis_key, redis_value = mock_config._BeamlineConfig__client.set.call_args.args
|
||||
redis_key, redis_value = mock_config._client.set.call_args.args
|
||||
assert redis_key == "x10sa:tell_events"
|
||||
|
||||
payload = json.loads(redis_value)
|
||||
|
||||
@@ -26,8 +26,8 @@ def aerotech_controller(mock_aerotech_api):
|
||||
):
|
||||
controller = AerotechController(MXBeamline.X10SA)
|
||||
|
||||
controller._AerotechController__api = mock_aerotech_api
|
||||
controller._AerotechController__simulated = False
|
||||
controller._api = mock_aerotech_api
|
||||
controller._simulated = False
|
||||
return controller
|
||||
|
||||
|
||||
@@ -36,13 +36,13 @@ def test_init_x10sa(mock_aerotech_api):
|
||||
"aare.devices.aerotech.cfg_get", return_value="http://mx-x10sa-queue-01.psi.ch:5234"
|
||||
):
|
||||
controller = AerotechController(MXBeamline.X10SA)
|
||||
assert controller._AerotechController__base == "http://mx-x10sa-queue-01.psi.ch:5234"
|
||||
assert controller._AerotechController__simulated is False
|
||||
assert controller._base == "http://mx-x10sa-queue-01.psi.ch:5234"
|
||||
assert controller._simulated is False
|
||||
|
||||
|
||||
def test_init_simulated():
|
||||
controller = AerotechController(MXBeamline.SIMULATED)
|
||||
assert controller._AerotechController__simulated is True
|
||||
assert controller._simulated is True
|
||||
|
||||
|
||||
def test_cancel(aerotech_controller, mock_aerotech_api):
|
||||
|
||||
@@ -18,7 +18,7 @@ def test_shutter_init(mock_pv):
|
||||
|
||||
@patch("aare.devices.experimental_hutch_shutter.PV")
|
||||
def test_shutter_state(mock_pv):
|
||||
# Mocking self.__state
|
||||
# Mocking self._state
|
||||
mock_state_pv = MagicMock()
|
||||
mock_pv.side_effect = [MagicMock(), MagicMock(), mock_state_pv]
|
||||
|
||||
|
||||
@@ -89,17 +89,17 @@ def create_mock_daq_status():
|
||||
|
||||
def test_init_x10sa(mock_jfjoch_client):
|
||||
wrapper = JFJochWrapper(MXBeamline.X10SA)
|
||||
assert wrapper._JFJochWrapper__url == "http://sls-gpu-002:8080"
|
||||
assert wrapper._url == "http://sls-gpu-002:8080"
|
||||
|
||||
|
||||
def test_init_x06da(mock_jfjoch_client):
|
||||
wrapper = JFJochWrapper(MXBeamline.X06DA)
|
||||
assert wrapper._JFJochWrapper__url == "http://sls-gpu-001:8080"
|
||||
assert wrapper._url == "http://sls-gpu-001:8080"
|
||||
|
||||
|
||||
def test_init_simulated(mock_jfjoch_client):
|
||||
wrapper = JFJochWrapper(MXBeamline.SIMULATED)
|
||||
assert wrapper._JFJochWrapper__simulated is True
|
||||
assert wrapper._simulated is True
|
||||
|
||||
|
||||
def test_init_unknown():
|
||||
|
||||
@@ -30,7 +30,7 @@ def camera_thread(mock_zmq, qtbot):
|
||||
|
||||
def test_init(camera_thread):
|
||||
assert camera_thread.running is True
|
||||
assert camera_thread._SampleCameraThread__camera_available is False
|
||||
assert camera_thread._camera_available is False
|
||||
|
||||
|
||||
def test_update_daq_status(camera_thread):
|
||||
@@ -38,15 +38,15 @@ def test_update_daq_status(camera_thread):
|
||||
s.geom.beam_location_pxl.x = 100
|
||||
s.geom.beam_location_pxl.y = 200
|
||||
camera_thread.update_daq_status(s)
|
||||
assert camera_thread._SampleCameraThread__beam_x == 100
|
||||
assert camera_thread._SampleCameraThread__beam_y == 200
|
||||
assert camera_thread._beam_x == 100
|
||||
assert camera_thread._beam_y == 200
|
||||
|
||||
|
||||
def test_enable_focus_measurement(camera_thread):
|
||||
camera_thread.enable_focus_measurement(True)
|
||||
assert camera_thread._SampleCameraThread__measure_focus is True
|
||||
assert camera_thread._measure_focus is True
|
||||
camera_thread.enable_focus_measurement(False)
|
||||
assert camera_thread._SampleCameraThread__measure_focus is False
|
||||
assert camera_thread._measure_focus is False
|
||||
|
||||
|
||||
def test_run_success(camera_thread, mock_zmq, qtbot):
|
||||
@@ -63,7 +63,7 @@ def test_run_success(camera_thread, mock_zmq, qtbot):
|
||||
with qtbot.waitSignal(camera_thread.camera_image, timeout=5000):
|
||||
camera_thread.run()
|
||||
|
||||
assert camera_thread._SampleCameraThread__camera_available is True
|
||||
assert camera_thread._camera_available is True
|
||||
|
||||
|
||||
def test_run_bayer_success(camera_thread, mock_zmq, qtbot):
|
||||
@@ -80,7 +80,7 @@ def test_run_bayer_success(camera_thread, mock_zmq, qtbot):
|
||||
with qtbot.waitSignal(camera_thread.camera_image, timeout=5000):
|
||||
camera_thread.run()
|
||||
|
||||
assert camera_thread._SampleCameraThread__camera_available is True
|
||||
assert camera_thread._camera_available is True
|
||||
|
||||
|
||||
def test_run_zmq_timeout(camera_thread, mock_zmq, qtbot):
|
||||
@@ -89,7 +89,7 @@ def test_run_zmq_timeout(camera_thread, mock_zmq, qtbot):
|
||||
raise zmq.Again()
|
||||
|
||||
mock_zmq.recv_multipart.side_effect = side_effect
|
||||
camera_thread._SampleCameraThread__fps_emit_period_s = 0.0
|
||||
camera_thread._fps_emit_period_s = 0.0
|
||||
|
||||
with qtbot.waitSignal(camera_thread.fps_measure, timeout=5000):
|
||||
try:
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import importlib
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from aare.beamline_dispatch.beamline_dispatch import get_beamline_dispatch
|
||||
from aare.beamline_dispatch.simulated import SimulatedDispatch
|
||||
from aare.beamline_dispatch.x06da.beamline_dispatch import X06daDispatch
|
||||
from aare.beamline_dispatch.x06sa import X06saDispatch
|
||||
from aare.beamline_dispatch.x10sa import X10saDispatch
|
||||
|
||||
|
||||
def test_dispatch_cannot_be_instantiated_without_env():
|
||||
with patch.dict("os.environ", {}, clear=True):
|
||||
with pytest.raises(ValueError) as e:
|
||||
_ = get_beamline_dispatch()
|
||||
assert e.match("Please set the BEAMLINE environment variable")
|
||||
|
||||
|
||||
def test_simulated_dispatch_can_be_instantiated():
|
||||
with patch.dict("os.environ", {"BEAMLINE": "SIMULATED"}):
|
||||
dispatch = get_beamline_dispatch()
|
||||
assert isinstance(dispatch, SimulatedDispatch)
|
||||
|
||||
|
||||
@pytest.mark.skipif(importlib.util.find_spec("pxi_bec") is None, reason="run only for pxi flavour")
|
||||
def test_pxi_dispatch_can_be_instantiated():
|
||||
with patch.dict("os.environ", {"BEAMLINE": "X06SA"}):
|
||||
with pytest.raises(TypeError) as e:
|
||||
_ = get_beamline_dispatch()
|
||||
assert e.match("Can't instantiate abstract class")
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
importlib.util.find_spec("pxii_bec") is None, reason="run only for pxii flavour"
|
||||
)
|
||||
def test_pxii_dispatch_can_be_instantiated():
|
||||
with patch.dict("os.environ", {"BEAMLINE": "X10SA"}):
|
||||
dispatch = get_beamline_dispatch()
|
||||
assert isinstance(dispatch, X10saDispatch)
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
importlib.util.find_spec("pxiii_bec") is None, reason="run only for pxiii flavour"
|
||||
)
|
||||
def test_pxiii_dispatch_can_be_instantiated():
|
||||
with patch.dict("os.environ", {"BEAMLINE": "X06DA"}):
|
||||
with pytest.raises(TypeError) as e:
|
||||
_ = get_beamline_dispatch()
|
||||
assert e.match("Can't instantiate abstract class")
|
||||
Reference in New Issue
Block a user