diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 5353b233..90e34916 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -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 diff --git a/scripts/camera_stat_thread.py b/scripts/camera_stat_thread.py index 8db972d2..5f444731 100644 --- a/scripts/camera_stat_thread.py +++ b/scripts/camera_stat_thread.py @@ -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() diff --git a/src/aare/beamline_dispatch/__init__.py b/src/aare/beamline_dispatch/__init__.py new file mode 100644 index 00000000..4d29ae1f --- /dev/null +++ b/src/aare/beamline_dispatch/__init__.py @@ -0,0 +1 @@ +"""A single API to dispatch functions based on the current beamline""" diff --git a/src/aare/beamline_dispatch/beamline_dispatch.py b/src/aare/beamline_dispatch/beamline_dispatch.py new file mode 100644 index 00000000..80e1bb47 --- /dev/null +++ b/src/aare/beamline_dispatch/beamline_dispatch.py @@ -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() diff --git a/src/aare/beamline_dispatch/default/__init__.py b/src/aare/beamline_dispatch/default/__init__.py new file mode 100644 index 00000000..85788a92 --- /dev/null +++ b/src/aare/beamline_dispatch/default/__init__.py @@ -0,0 +1,3 @@ +from .beamline_dispatch import DefaultDispatch + +__all__ = ["DefaultDispatch"] diff --git a/src/aare/beamline_dispatch/default/beamline_dispatch.py b/src/aare/beamline_dispatch/default/beamline_dispatch.py new file mode 100644 index 00000000..64daae3a --- /dev/null +++ b/src/aare/beamline_dispatch/default/beamline_dispatch.py @@ -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 diff --git a/src/aare/beamline_dispatch/protocols.py b/src/aare/beamline_dispatch/protocols.py new file mode 100644 index 00000000..d0675a7a --- /dev/null +++ b/src/aare/beamline_dispatch/protocols.py @@ -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: ... diff --git a/src/aare/beamline_dispatch/simulated/__init__.py b/src/aare/beamline_dispatch/simulated/__init__.py new file mode 100644 index 00000000..2f069aaf --- /dev/null +++ b/src/aare/beamline_dispatch/simulated/__init__.py @@ -0,0 +1,3 @@ +from .beamline_dispatch import SimulatedDispatch + +__all__ = ["SimulatedDispatch"] diff --git a/src/aare/beamline_dispatch/simulated/beamline_dispatch.py b/src/aare/beamline_dispatch/simulated/beamline_dispatch.py new file mode 100644 index 00000000..59fcbd14 --- /dev/null +++ b/src/aare/beamline_dispatch/simulated/beamline_dispatch.py @@ -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 diff --git a/src/aare/beamline_dispatch/x06da/__init__.py b/src/aare/beamline_dispatch/x06da/__init__.py new file mode 100644 index 00000000..6c356917 --- /dev/null +++ b/src/aare/beamline_dispatch/x06da/__init__.py @@ -0,0 +1,3 @@ +from .beamline_dispatch import X06daDispatch + +__all__ = ["X06daDispatch"] diff --git a/src/aare/beamline_dispatch/x06da/beamline_dispatch.py b/src/aare/beamline_dispatch/x06da/beamline_dispatch.py new file mode 100644 index 00000000..7aab4be3 --- /dev/null +++ b/src/aare/beamline_dispatch/x06da/beamline_dispatch.py @@ -0,0 +1,4 @@ +from aare.beamline_dispatch.default.beamline_dispatch import DefaultDispatch + + +class X06daDispatch(DefaultDispatch): ... diff --git a/src/aare/beamline_dispatch/x06sa/__init__.py b/src/aare/beamline_dispatch/x06sa/__init__.py new file mode 100644 index 00000000..359dbc5a --- /dev/null +++ b/src/aare/beamline_dispatch/x06sa/__init__.py @@ -0,0 +1,3 @@ +from .beamline_dispatch import X06saDispatch + +__all__ = ["X06saDispatch"] diff --git a/src/aare/beamline_dispatch/x06sa/beamline_dispatch.py b/src/aare/beamline_dispatch/x06sa/beamline_dispatch.py new file mode 100644 index 00000000..fcae2cec --- /dev/null +++ b/src/aare/beamline_dispatch/x06sa/beamline_dispatch.py @@ -0,0 +1,4 @@ +from aare.beamline_dispatch.default.beamline_dispatch import DefaultDispatch + + +class X06saDispatch(DefaultDispatch): ... diff --git a/src/aare/beamline_dispatch/x10sa/__init__.py b/src/aare/beamline_dispatch/x10sa/__init__.py new file mode 100644 index 00000000..7e56c8b7 --- /dev/null +++ b/src/aare/beamline_dispatch/x10sa/__init__.py @@ -0,0 +1,3 @@ +from .beamline_dispatch import X10saDispatch + +__all__ = ["X10saDispatch"] diff --git a/src/aare/beamline_dispatch/x10sa/beamline_dispatch.py b/src/aare/beamline_dispatch/x10sa/beamline_dispatch.py new file mode 100644 index 00000000..380513cb --- /dev/null +++ b/src/aare/beamline_dispatch/x10sa/beamline_dispatch.py @@ -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 diff --git a/src/aare/daq/aaredb.py b/src/aare/daq/aaredb.py index b2b57cca..2ceeec9a 100644 --- a/src/aare/daq/aaredb.py +++ b/src/aare/daq/aaredb.py @@ -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() diff --git a/src/aare/daq/auth.py b/src/aare/daq/auth.py index f7fbf429..927478b2 100644 --- a/src/aare/daq/auth.py +++ b/src/aare/daq/auth.py @@ -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: diff --git a/src/aare/daq/config.py b/src/aare/daq/config.py index 0f80f7c8..47d71310 100644 --- a/src/aare/daq/config.py +++ b/src/aare/daq/config.py @@ -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}") diff --git a/src/aare/daq/daq.py b/src/aare/daq/daq.py index 875ddfe5..402b6bd9 100644 --- a/src/aare/daq/daq.py +++ b/src/aare/daq/daq.py @@ -165,7 +165,7 @@ class _DAQPGroupProvider: @property def pgroup(self) -> str | None: - return self._daq._AareDAQ__cfg.pgroup + return self._daq._cfg.pgroup class _DAQStatusProvider: @@ -182,7 +182,7 @@ class _DAQStateSetter: self._daq = daq def set_state(self, target: BeamlineStateEnum) -> None: - self._daq._AareDAQ__set_state(target) + self._daq._set_state(target) class _DAQTraceAppender: @@ -198,7 +198,7 @@ class _DAQSampleEventSender: self._daq = daq def send_sample_event(self, sample_id: int, event_type, comment: str | None = None) -> None: - self._daq._AareDAQ__aare.send_sample_event(sample_id, event_type, comment) + self._daq._aare.send_sample_event(sample_id, event_type, comment) class _DAQScanIngestor: @@ -206,14 +206,14 @@ class _DAQScanIngestor: self._daq = daq def ingest_scan(self, *, sample, result, geom, beam_mark_pxl) -> None: - self._daq._AareDAQ__aare.ingest_scan( + self._daq._aare.ingest_scan( sample=sample, result=result, geom=geom, beam_mark_pxl=beam_mark_pxl ) def ingest_gridscan( self, *, sample, raster_result, raster_request, geom, com, beam_mark_pxl ) -> None: - self._daq._AareDAQ__aare.ingest_gridscan( + self._daq._aare.ingest_gridscan( sample=sample, raster_result=raster_result, raster_request=raster_request, @@ -228,7 +228,7 @@ class _DAQDatacollectionSetupRunner: self._daq = daq def prepare(self, request, screening: bool = False) -> None: - self._daq._AareDAQ__setup_datacollection(request=request, screening=screening) + self._daq._setup_datacollection(request=request, screening=screening) class _LoopCenteringPredictionGetter: @@ -237,7 +237,7 @@ class _LoopCenteringPredictionGetter: self._settings = settings def get_predictions(self): - return self._daq._AareDAQ__mlbox.predict_all_best( + return self._daq._mlbox.predict_all_best( overlap_with_pin=self._settings.overlap_with_pin, confidence_min=self._settings.confidence_min, return_image=True, @@ -272,14 +272,14 @@ class AareDAQ: def __init__(self, cfg: BeamlineConfig, bl: MXBeamline): self.last_time = 0.0 - self.__cfg = cfg - self.__devs = BeamlineDevices(bl) - self.__mlbox = MlBox(bl) - self.__jfjoch = JFJochWrapper(bl) - self.__bl = bl.value.upper() + self._cfg = cfg + self._devs = BeamlineDevices(bl) + self._mlbox = MlBox(bl) + self._jfjoch = JFJochWrapper(bl) + self._bl = bl.value.upper() self._beamline = bl - self.__aare = AareWrapper(bl) - self.__saved_box = None + self._aare = AareWrapper(bl) + self._saved_box = None self._smargon_trace_path = Path("/sls/mx/applications/logs") / "smargon_trace.csv" self._face_detection_progress_cb: Callable[[dict], None] | None = None self._automation_progress_cb: Callable[[AutomationProgress], None] | None = None @@ -292,8 +292,8 @@ class AareDAQ: self._last_mount_error_message = "" self._screenshot_service = ScreenshotService( - mlbox=self.__mlbox, - aare=self.__aare, + mlbox=self._mlbox, + aare=self._aare, logger=logger, run_noncritical=_DAQNonCriticalRunner(self), sample_provider=_DAQScreenshotSampleProvider(self), @@ -301,14 +301,14 @@ class AareDAQ: ) def _cached_detector_metadata(self) -> dict: - return self.__cfg.cached_detector_metadata + return self._cfg.cached_detector_metadata def refresh_detector_metadata_cache(self) -> dict[str, object]: - det_cfg = self.__jfjoch.detector() - dtz_low = self.__cfg._coerce_optional_float(self.__devs.dtz_low) - dtz_high = self.__cfg._coerce_optional_float(self.__devs.dtz_high) + det_cfg = self._jfjoch.detector() + dtz_low = self._cfg._coerce_optional_float(self._devs.dtz_low) + dtz_high = self._cfg._coerce_optional_float(self._devs.dtz_high) - payload = self.__cfg.set_detector_metadata( + payload = self._cfg.set_detector_metadata( { "detector_description": det_cfg.description, "detector_serial_number": det_cfg.serial_number, @@ -331,10 +331,10 @@ class AareDAQ: return payload def get_runtime_simulation_state(self) -> dict[str, bool]: - return self.__cfg.runtime_simulation_state + return self._cfg.runtime_simulation_state def get_local_contact_links(self) -> dict[str, str | None]: - return self.__cfg.local_contact_links + return self._cfg.local_contact_links def get_local_contact_device_state(self) -> dict[str, dict[str, str | bool | None]]: status = self.status @@ -358,68 +358,68 @@ class AareDAQ: } def restart_bec_worker(self) -> dict[str, object]: - self.__cfg.try_set_busy(timeout=360) + self._cfg.try_set_busy(timeout=360) try: - self.__devs.restart_bec_worker(simulated=self.__cfg.simulate_bec) - return {"ok": True, "device": "bec", "simulated": self.__cfg.simulate_bec} + self._devs.restart_bec_worker(simulated=self._cfg.simulate_bec) + return {"ok": True, "device": "bec", "simulated": self._cfg.simulate_bec} finally: - self.__cfg.state_busy = False + self._cfg.state_busy = False def restart_detector(self) -> dict[str, object]: - self.__cfg.try_set_busy(timeout=360) + self._cfg.try_set_busy(timeout=360) try: - beamline = MXBeamline.SIMULATED if self.__cfg.simulated_detector else self._beamline - logger.info(f"Restarting JFJoch wrapper with simulated={self.__cfg.simulated_detector}") - self.__jfjoch = JFJochWrapper(beamline) + beamline = MXBeamline.SIMULATED if self._cfg.simulated_detector else self._beamline + logger.info(f"Restarting JFJoch wrapper with simulated={self._cfg.simulated_detector}") + self._jfjoch = JFJochWrapper(beamline) return { "ok": True, "device": "detector", - "simulated": bool(self.__cfg.simulated_detector), + "simulated": bool(self._cfg.simulated_detector), } finally: - self.__cfg.state_busy = False + self._cfg.state_busy = False def restart_tell(self) -> dict[str, object]: - self.__cfg.try_set_busy(timeout=360) + self._cfg.try_set_busy(timeout=360) try: - self.__devs.restart_tell(simulated=self.__cfg.simulate_tell) - return {"ok": True, "device": "tell", "simulated": self.__cfg.simulate_tell} + self._devs.restart_tell(simulated=self._cfg.simulate_tell) + return {"ok": True, "device": "tell", "simulated": self._cfg.simulate_tell} finally: - self.__cfg.state_busy = False + self._cfg.state_busy = False def restart_aerotech(self) -> dict[str, object]: - self.__cfg.try_set_busy(timeout=360) + self._cfg.try_set_busy(timeout=360) try: - self.__devs.restart_aerotech(simulated=self.__cfg.simulate_aerotech) - return {"ok": True, "device": "aerotech", "simulated": self.__cfg.simulate_aerotech} + self._devs.restart_aerotech(simulated=self._cfg.simulate_aerotech) + return {"ok": True, "device": "aerotech", "simulated": self._cfg.simulate_aerotech} finally: - self.__cfg.state_busy = False + self._cfg.state_busy = False def restart_smargon(self) -> dict[str, object]: - self.__cfg.try_set_busy(timeout=360) + self._cfg.try_set_busy(timeout=360) try: - self.__devs.restart_smargon(simulated=self.__cfg.simulate_smargon) - return {"ok": True, "device": "smargon", "simulated": self.__cfg.simulate_smargon} + self._devs.restart_smargon(simulated=self._cfg.simulate_smargon) + return {"ok": True, "device": "smargon", "simulated": self._cfg.simulate_smargon} finally: - self.__cfg.state_busy = False + self._cfg.state_busy = False def set_runtime_simulation(self, device: str, enabled: bool) -> dict[str, object]: device = str(device).strip().lower() if device == "bec": - self.__cfg.simulate_bec = enabled + self._cfg.simulate_bec = enabled return self.restart_bec_worker() elif device == "detector": - self.__cfg.simulated_detector = enabled + self._cfg.simulated_detector = enabled return self.restart_detector() elif device == "tell": - self.__cfg.simulate_tell = enabled + self._cfg.simulate_tell = enabled return self.restart_tell() elif device == "aerotech": - self.__cfg.simulate_aerotech = enabled + self._cfg.simulate_aerotech = enabled return self.restart_aerotech() elif device == "smargon": - self.__cfg.simulate_smargon = enabled + self._cfg.simulate_smargon = enabled return self.restart_smargon() raise ValueError( @@ -501,14 +501,14 @@ class AareDAQ: ) from error def _ensure_not_in_maintenance(self, *, context: str) -> None: - if self.__cfg.state == BeamlineStateEnum.Maintenance: + if self._cfg.state == BeamlineStateEnum.Maintenance: raise MaintenanceStateException( f"Automation cannot continue because beamline is in Maintenance during {context}" ) def _validate_automation_state(self, *, context: str) -> None: self._ensure_not_in_maintenance(context=context) - if self.__cfg.state == BeamlineStateEnum.Moving: + if self._cfg.state == BeamlineStateEnum.Moving: raise StateTransitionFailed( f"Automation cannot continue because beamline is still in Moving state during {context}" ) @@ -566,8 +566,8 @@ class AareDAQ: def _get_tell_events_from_redis(self) -> list[dict]: try: - redis_client = getattr(self._AareDAQ__cfg, "_BeamlineConfig__client", None) - beamline_key = getattr(self._AareDAQ__cfg, "_BeamlineConfig__bl", None) + redis_client = getattr(self._cfg, "_client", None) + beamline_key = getattr(self._cfg, "_bl", None) if redis_client is None or beamline_key is None: return [] @@ -869,9 +869,7 @@ class AareDAQ: return LoopCenteringService( context=LoopCenteringContext( - deps=LoopCenteringDependencies( - cfg=self.__cfg, devs=self.__devs, mlbox=self.__mlbox - ), + deps=LoopCenteringDependencies(cfg=self._cfg, devs=self._devs, mlbox=self._mlbox), runtime=self._build_runtime_state(), services=services, settings=settings, @@ -887,9 +885,7 @@ class AareDAQ: return FaceDetectionService( context=FaceDetectionContext( - deps=FaceDetectionDependencies( - cfg=self.__cfg, devs=self.__devs, mlbox=self.__mlbox - ), + deps=FaceDetectionDependencies(cfg=self._cfg, devs=self._devs, mlbox=self._mlbox), runtime=self._build_runtime_state(), services=services, settings=FaceDetectionSettings(), @@ -900,7 +896,7 @@ class AareDAQ: def _create_mounting_service(self) -> MountingService: return MountingService( context=MountingContext( - deps=MountingDependencies(cfg=self.__cfg, devs=self.__devs), + deps=MountingDependencies(cfg=self._cfg, devs=self._devs), settings=MountingSettings(mount_position=ABR_POS_MOUNT), ), logger=logger, @@ -910,11 +906,11 @@ class AareDAQ: return RasterService( context=RasterContext( deps=RasterDependencies( - cfg=self.__cfg, - devs=self.__devs, - mlbox=self.__mlbox, - jfjoch=self.__jfjoch, - aare=self.__aare, + cfg=self._cfg, + devs=self._devs, + mlbox=self._mlbox, + jfjoch=self._jfjoch, + aare=self._aare, ), runtime=self._build_runtime_state(), services=self._build_operation_services(), @@ -931,7 +927,7 @@ class AareDAQ: return RotationService( context=RotationContext( deps=RotationDependencies( - cfg=self.__cfg, devs=self.__devs, jfjoch=self.__jfjoch, aare=self.__aare + cfg=self._cfg, devs=self._devs, jfjoch=self._jfjoch, aare=self._aare ), runtime=self._build_runtime_state(), services=self._build_operation_services(), @@ -984,7 +980,7 @@ class AareDAQ: comment += f" Additional comment: {additional_comment}" try: - self.__aare.send_sample_event( + self._aare.send_sample_event( sample_id=sample_id, event_type=event_type, comment=comment ) except Exception: @@ -1026,14 +1022,14 @@ class AareDAQ: ) if previous_sample is not None and previous_sample.db_id is not None: - self.__aare.send_sample_event(previous_sample.db_id, SampleEventType.UNMOUNTING) + self._aare.send_sample_event(previous_sample.db_id, SampleEventType.UNMOUNTING) - self.__set_state(BeamlineStateEnum.RobotSampleExchange) + self._set_state(BeamlineStateEnum.RobotSampleExchange) if sample is not None and sample.db_id is not None: - self.__aare.send_sample_event(sample.db_id, SampleEventType.MOUNTING) + self._aare.send_sample_event(sample.db_id, SampleEventType.MOUNTING) - self.__devs.tell.blower_on() + self._devs.tell.blower_on() mounting_result: MountingResult = self._create_mounting_service().execute(target=sample) if not mounting_result.success: @@ -1049,12 +1045,12 @@ class AareDAQ: and previous_sample is not None and previous_sample.db_id is not None ): - self.__aare.send_sample_event(previous_sample.db_id, SampleEventType.UNMOUNTED) + self._aare.send_sample_event(previous_sample.db_id, SampleEventType.UNMOUNTED) - self.__set_state(BeamlineStateEnum.SampleAlignment) + self._set_state(BeamlineStateEnum.SampleAlignment) if mounted_sample is not None and mounted_sample.db_id is not None: - self.__aare.send_sample_event(mounted_sample.db_id, SampleEventType.MOUNTED) + self._aare.send_sample_event(mounted_sample.db_id, SampleEventType.MOUNTED) self.save_screenshot_db(mounted_sample.db_id, f"{mounted_sample.db_id}_mounted") return True @@ -1096,15 +1092,15 @@ class AareDAQ: "Mount failed after TELL confirmed previous sample was unmounted; " "marking previous sample as unmounted and clearing cached current_sample" ) - self.__cfg.current_sample = None - self.__aare.send_sample_event( + self._cfg.current_sample = None + self._aare.send_sample_event( previous_sample.db_id, SampleEventType.UNMOUNTED, comment="Auto-unmount succeeded before mount failed", ) try: - self.__set_state(BeamlineStateEnum.SampleAlignment) + self._set_state(BeamlineStateEnum.SampleAlignment) except Exception: logger.exception("Failed to set state to SampleAlignment") finally: @@ -1131,11 +1127,11 @@ class AareDAQ: logger.error("Loop centering failed: no sample provided") return False - self.__set_state(BeamlineStateEnum.SampleAlignment) - self.__devs.lamp_light = 2.5 + self._set_state(BeamlineStateEnum.SampleAlignment) + self._devs.lamp_light = 2.5 try: - self.__aare.send_sample_event(sample.db_id, SampleEventType.CENTERING) + self._aare.send_sample_event(sample.db_id, SampleEventType.CENTERING) service = self._create_loop_centering_service() result = service.run(sample_id=sample.db_id) @@ -1151,7 +1147,7 @@ class AareDAQ: return False self.save_screenshot_db(sample.db_id, "loop_centering") - self.__aare.send_sample_event(sample.db_id, SampleEventType.CENTERED) + self._aare.send_sample_event(sample.db_id, SampleEventType.CENTERED) return True except Exception as e: @@ -1181,7 +1177,7 @@ class AareDAQ: Returns: FaceDetectionResult """ - self.__set_state(BeamlineStateEnum.SampleAlignment) + self._set_state(BeamlineStateEnum.SampleAlignment) result: FaceDetectionResult | None = None try: @@ -1193,7 +1189,7 @@ class AareDAQ: logger.error("Failed to get current sample") sample = None - aare = getattr(self, "_AareDAQ__aare", None) + aare = getattr(self, "_aare", None) if aare is not None and sample is not None and sample.db_id is not None: aare.send_sample_event(sample.db_id, SampleEventType.LOOPFACEDETECTING) @@ -1271,7 +1267,7 @@ class AareDAQ: if auto_center: setup_request = copy.deepcopy(grid_request) setup_request.smargon_top_left = None - self.__setup_datacollection(request=setup_request) + self._setup_datacollection(request=setup_request) result = raster_service.execute_auto_center(grid_request) if result is None: logger.error( @@ -1285,20 +1281,20 @@ class AareDAQ: # Resolve + validate the requested dtz (sets cfg.dtz, requires # request.dtz) BEFORE configuring JFJoch, so JFJoch receives the # commanded target rather than the live, still-in-transit position. - self.__setup_datacollection(request=grid_request) - logger.debug(f"Is detector simulated? {self.__cfg.simulated_detector}") - if not self.__cfg.simulated_detector: + self._setup_datacollection(request=grid_request) + logger.debug(f"Is detector simulated? {self._cfg.simulated_detector}") + if not self._cfg.simulated_detector: logger.info("initialise detector for raster") status = self.status - self.__jfjoch.measure_raster(grid_request, status) + self._jfjoch.measure_raster(grid_request, status) logger.info("detector initialised") else: logger.info("Simulated detector mode enabled; using fake raster result.") - self.__set_state(BeamlineStateEnum.DataCollection) + self._set_state(BeamlineStateEnum.DataCollection) raster_result = raster_service.execute(grid_request) result = CompletedRasterGrid(r=[raster_result]) - self.__set_state(BeamlineStateEnum.SampleAlignment) + self._set_state(BeamlineStateEnum.SampleAlignment) if result is not None: logger.info( @@ -1316,9 +1312,7 @@ class AareDAQ: ) else: if self.sample is not None and self.sample.db_id is not None: - self.__aare.send_sample_event( - self.sample.db_id, SampleEventType.RASTERINGFAILED - ) + self._aare.send_sample_event(self.sample.db_id, SampleEventType.RASTERINGFAILED) logger.error( "Raster sequence returned no result", extra=merge_log_context( @@ -1468,11 +1462,11 @@ class AareDAQ: ) def _find_sample_by_mounted_address(self, mounted_address) -> SampleShortInfo | None: - for sample in self.__cfg.spreadsheet.s: + for sample in self._cfg.spreadsheet.s: if self._sample_matches_mounted_address(sample, mounted_address): return sample - for sample in self.__cfg.reference_tools.s: + for sample in self._cfg.reference_tools.s: if self._sample_matches_mounted_address(sample, mounted_address): return sample @@ -1504,7 +1498,7 @@ class AareDAQ: def sync_current_sample_from_tell( self, force: bool = False, clear_cached_on_empty: bool = True ) -> SampleShortInfo | None: - current_sample = self.__cfg.current_sample + current_sample = self._cfg.current_sample if current_sample is not None and current_sample.location is None: return current_sample @@ -1514,18 +1508,18 @@ class AareDAQ: return current_sample self._last_sample_sync_ts = now - mounted_address = self.__devs.tell.get_mounted_sample() + mounted_address = self._devs.tell.get_mounted_sample() if mounted_address is None: if current_sample is not None and current_sample.location is not None: if clear_cached_on_empty: logger.warning("TELL reports no mounted sample; clearing cached current_sample") - self.__cfg.current_sample = None + self._cfg.current_sample = None else: logger.warning( "TELL reports no mounted sample; keeping cached current_sample to avoid losing context" ) - return self.__cfg.current_sample + return self._cfg.current_sample if self._sample_matches_mounted_address(current_sample, mounted_address): return current_sample @@ -1543,32 +1537,32 @@ class AareDAQ: extra={"db_id": resolved_sample.db_id}, ) - self.__cfg.current_sample = resolved_sample + self._cfg.current_sample = resolved_sample return resolved_sample @property def state(self) -> BeamlineStateEnum: - return self.__cfg.state + return self._cfg.state @property def busy(self) -> bool: - return self.__cfg.state_busy + return self._cfg.state_busy @state.setter def state(self, target: BeamlineStateEnum): if target == BeamlineStateEnum.Moving: logger.error( "Cannot explicitly move to busy state", - extra={"target": target, "state": self.__cfg.state}, + extra={"target": target, "state": self._cfg.state}, ) raise Exception("Cannot explicitly move to busy state") start = time.perf_counter() - self.__cfg.try_set_busy(timeout=300) + self._cfg.try_set_busy(timeout=300) try: - self.__set_state(target) + self._set_state(target) finally: - self.__cfg.state_busy = False + self._cfg.state_busy = False end = time.perf_counter() @@ -1652,73 +1646,73 @@ class AareDAQ: @property def omega(self) -> float: - return self.__devs.aerotech_omega + return self._devs.aerotech_omega - def __omega(self, val: float): - self.__saved_box = None + def _omega(self, val: float): + self._saved_box = None logger.info(f"Set omega to {val}") if -2000 < val < 2000: try: - self.__devs.aerotech_omega = val - self.__cfg.state_busy = False + self._devs.aerotech_omega = val + self._cfg.state_busy = False except Exception as e: logger.error(f"Omega error: {e}") - self.__cfg.state_busy = False + self._cfg.state_busy = False logger.error("Omega move timed out") else: - self.__cfg.state_busy = False + self._cfg.state_busy = False logger.error("Omega has to be between -2000 and 2000 degrees") raise ValueError("Omega has to be between -2000 and 2000 degrees (for now)") @omega.setter def omega(self, val: float): - self.__cfg.set_busy(BeamlineStateEnum.SampleAlignment) - self.__omega(val) + self._cfg.set_busy(BeamlineStateEnum.SampleAlignment) + self._omega(val) def omega_rel(self, val: float): - self.__cfg.set_busy(BeamlineStateEnum.SampleAlignment) - curr_omega = self.__devs.aerotech_omega - self.__omega(curr_omega + val) + self._cfg.set_busy(BeamlineStateEnum.SampleAlignment) + curr_omega = self._devs.aerotech_omega + self._omega(curr_omega + val) @property def zoom(self) -> float: - return self.__devs.zoom + return self._devs.zoom @zoom.setter def zoom(self, val: float): - self.__saved_box = None + self._saved_box = None # Decide off the beamline STATE (persisted in Redis) so it is robust to # how/when beam-location was entered, rather than a separate flag that # must be set exactly on the state transition. - beam_location = self.__cfg.state == BeamlineStateEnum.BeamLocation + beam_location = self._cfg.state == BeamlineStateEnum.BeamLocation logger.debug( - f"zoom -> {val} (state={self.__cfg.state}, beam_location_presets={beam_location})" + f"zoom -> {val} (state={self._cfg.state}, beam_location_presets={beam_location})" ) if beam_location: # Auto-exposure is too dark to see the beam at high zoom, so apply # the preset per-zoom gain/exposure (interpolated between the stored # zoom stops). Every other state keeps auto-exposure. - if self.__cfg.zoom_mode != ZoomModeEnum.BeamLocation: - self.__cfg.zoom_mode = ZoomModeEnum.BeamLocation - self.__devs.zoom = val + if self._cfg.zoom_mode != ZoomModeEnum.BeamLocation: + self._cfg.zoom_mode = ZoomModeEnum.BeamLocation + self._devs.zoom = val time.sleep(0.2) - settings = self.__cfg.zoom_settings.get_camera_settings(val) - self.__devs.samcam_settings = settings + settings = self._cfg.zoom_settings.get_camera_settings(val) + self._devs.samcam_settings = settings logger.debug( f"applied beam-location preset for zoom {val}: gain={settings.gain}, exposure={settings.exposure}" ) else: - self.__devs.samcam_auto(AutoEnum.AUTO) - self.__devs.zoom = val + self._devs.samcam_auto(AutoEnum.AUTO) + self._devs.zoom = val time.sleep(0.2) - self.__devs.samcam_auto(AutoEnum.ONCE) + self._devs.samcam_auto(AutoEnum.ONCE) def save_beam_location_camera_setting(self) -> None: """Persist the camera's current gain/exposure for the current zoom as the beam-location preset (Redis), so it is re-applied on future zooms.""" zoom_value = self.zoom settings = self.samcam_settings - self.__cfg.save_zoom_camera_setting(zoom_value, settings, mode=ZoomModeEnum.BeamLocation) + self._cfg.save_zoom_camera_setting(zoom_value, settings, mode=ZoomModeEnum.BeamLocation) logger.info( f"Saved beam-location camera setting for zoom {zoom_value}: " f"gain={settings.gain}, exposure={settings.exposure}" @@ -1726,7 +1720,7 @@ class AareDAQ: @property def front_light(self) -> float: - val = self.__devs.lamp_light + val = self._devs.lamp_light if val <= 1.0: return 0 elif val >= 2.5: @@ -1738,11 +1732,11 @@ class AareDAQ: def front_light(self, f: float): conv = (f / 100.0 * 1.5) + 1.0 logger.info(f"Light {f} -> {conv}") - self.__devs.lamp_light = conv + self._devs.lamp_light = conv @property def back_light(self) -> float: - val = self.__devs.back_light + val = self._devs.back_light if val <= 0.0: return 0.0 elif val <= 3.0: @@ -1754,133 +1748,133 @@ class AareDAQ: def back_light(self, f: float): conv = f / 100.0 * 3.0 logger.info(f"Backlight {f} -> {conv}") - self.__devs.back_light = conv + self._devs.back_light = conv @property def sample(self) -> SampleShortInfo | None: - return self.__cfg.current_sample + return self._cfg.current_sample @property def samcam_settings(self) -> SampleCameraSettings: - return self.__devs.samcam_settings + return self._devs.samcam_settings @samcam_settings.setter def samcam_settings(self, s: SampleCameraSettings): - self.__devs.samcam_settings = s + self._devs.samcam_settings = s def tweak_abr_meas_pos(self, c: AerotechCoordinate): - self.__cfg.set_busy(BeamlineStateEnum.SampleAlignment) + self._cfg.set_busy(BeamlineStateEnum.SampleAlignment) try: - new_meas_pos = AerotechCoordinate(at_mm=self.__cfg.abr_meas_pos.at_mm + c.at_mm) - self.__cfg.abr_meas_pos = new_meas_pos - self.__devs.aerotech_pos = new_meas_pos - self.__saved_box = None - self.__cfg.state_busy = False + new_meas_pos = AerotechCoordinate(at_mm=self._cfg.abr_meas_pos.at_mm + c.at_mm) + self._cfg.abr_meas_pos = new_meas_pos + self._devs.aerotech_pos = new_meas_pos + self._saved_box = None + self._cfg.state_busy = False except Exception: - self.__cfg.state_busy = False + self._cfg.state_busy = False raise def save_abr_meas_pos(self): - self.__cfg.set_busy(BeamlineStateEnum.SampleAlignment) + self._cfg.set_busy(BeamlineStateEnum.SampleAlignment) try: - self.__cfg.abr_meas_pos = AerotechCoordinate(at_mm=self.__devs.aerotech_pos.at_mm) - self.__devs.bec_worker.save_current_aerotech_position() - self.__cfg.state_busy = False + self._cfg.abr_meas_pos = AerotechCoordinate(at_mm=self._devs.aerotech_pos.at_mm) + self._devs.bec_worker.save_current_aerotech_position() + self._cfg.state_busy = False except Exception: - self.__cfg.state_busy = False + self._cfg.state_busy = False raise def goto_abr_meas_pos(self): - self.__cfg.set_busy(BeamlineStateEnum.SampleAlignment) + self._cfg.set_busy(BeamlineStateEnum.SampleAlignment) try: - self.__devs.aerotech_pos = self.__cfg.abr_meas_pos - self.__cfg.state_busy = False + self._devs.aerotech_pos = self._cfg.abr_meas_pos + self._cfg.state_busy = False except Exception: - self.__cfg.state_busy = False + self._cfg.state_busy = False raise def create_sample(self, target: SampleShortInfo): - self.__cfg.try_set_busy(timeout=360) + self._cfg.try_set_busy(timeout=360) try: - curr_sample = self.__cfg.current_sample + curr_sample = self._cfg.current_sample if curr_sample is not None and curr_sample.location is not None: raise Exception("Sample from TELL is loaded") - self.__aare.create_manual_sample(target) - self.__cfg.current_sample = target - self.__cfg.state_busy = False + self._aare.create_manual_sample(target) + self._cfg.current_sample = target + self._cfg.state_busy = False except Exception: - self.__cfg.state_busy = False + self._cfg.state_busy = False raise def check_tell_mount_start_conditions(self) -> None: - self.__devs.tell.validate_mount_start_conditions() + self._devs.tell.validate_mount_start_conditions() def _execute_dry(self, park: bool = True, unmount: bool = False): self._create_mounting_service().dry(park=park, unmount=unmount) def park_and_dry(self, park=True, unmount: bool = False): - self.__cfg.try_set_busy(timeout=360) + self._cfg.try_set_busy(timeout=360) try: - self.__set_state(BeamlineStateEnum.RobotSampleExchange) + self._set_state(BeamlineStateEnum.RobotSampleExchange) except TransformationInvalidException as e: logger.error(f"Failed to go to robot sample exchange: {e}") logger.warning("trying to day and park without unmounting first") try: self._execute_dry(park=park, unmount=unmount) - self.__cfg.state_busy = False + self._cfg.state_busy = False except Exception as e: - self.__cfg.state_busy = False + self._cfg.state_busy = False logger.error(f"Failed to park and dry: {e}") raise def tell_toggle_blower(self): try: - self.__devs.tell.toggle_blower() + self._devs.tell.toggle_blower() except Exception as e: logger.error(f"Failed to turn off blower: {e}") def initialise_smargon(self): - self.__cfg.try_set_busy(timeout=360) + self._cfg.try_set_busy(timeout=360) try: - self.__devs.smargon_initialize() - self.__cfg.state_busy = False + self._devs.smargon_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 Smargon: {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 def recovery_unmount_sample(self) -> None: - self.__cfg.try_set_busy(timeout=360) + self._cfg.try_set_busy(timeout=360) try: - self.__set_state(BeamlineStateEnum.RobotSampleExchange) - self.__devs.tell.check_enable_motion() - self.__devs.tell.wait_not_busy() - self.__devs.tell.set_in_mount_position(True) - self.__devs.tell.unmount(wait=True, timeout=360.0) - self.__cfg.current_sample = None - self.__set_state(BeamlineStateEnum.SampleAlignment) - self.__cfg.state_busy = False + self._set_state(BeamlineStateEnum.RobotSampleExchange) + self._devs.tell.check_enable_motion() + self._devs.tell.wait_not_busy() + self._devs.tell.set_in_mount_position(True) + self._devs.tell.unmount(wait=True, timeout=360.0) + self._cfg.current_sample = None + self._set_state(BeamlineStateEnum.SampleAlignment) + self._cfg.state_busy = False except Exception: - self.__cfg.state_busy = False + self._cfg.state_busy = False raise @sample.setter def sample(self, target: SampleShortInfo | None): - self.__cfg.try_set_busy(timeout=360) + self._cfg.try_set_busy(timeout=360) try: logger.debug(f"Mount target {target}") @@ -1892,24 +1886,24 @@ class AareDAQ: operation_name = "Mount" if not self._execute_mount_and_prepare(target): - current_sample = target or self.__cfg.current_sample + current_sample = target or self._cfg.current_sample sample_name = self._sample_mount_display_name(current_sample) if target is None: raise UnmountingFailed(f"Failed to {operation_name.lower()} {sample_name}") raise MountingFailed(f"Failed to {operation_name.lower()} {sample_name}") logger.info(f"Sample operation completed: {target}") - self.__cfg.state_busy = False + self._cfg.state_busy = False except Exception as e: - self.__cfg.state_busy = False + self._cfg.state_busy = False logger.debug(f"Failed to change mounted sample: {e}") raise def list_loaded_pucks(self) -> List[PuckLoadedInfo]: return [] - def __auto_focus(self, settings: AutofocusSettings, settle_time_s: float = 1.0) -> float: + def _auto_focus(self, settings: AutofocusSettings, settle_time_s: float = 1.0) -> float: # TODO uses old code change """ Scan smargon Z and find the position with maximum focus measure. @@ -1933,20 +1927,20 @@ class AareDAQ: Returns: Best Z position (mm) found during the scan """ - self.__cfg.set_busy(BeamlineStateEnum.SampleAlignment) + self._cfg.set_busy(BeamlineStateEnum.SampleAlignment) try: - best_z = self.__auto_focus(settings) - self.__cfg.state_busy = False + best_z = self._auto_focus(settings) + self._cfg.state_busy = False return best_z except Exception as e: logger.error(f"Autofocus failed: {e}") - self.__cfg.state_busy = False + self._cfg.state_busy = False raise def auto_exposure(self): - self.__devs.samcam_auto(AutoEnum.ONCE) + self._devs.samcam_auto(AutoEnum.ONCE) - def __setup_datacollection( + def _setup_datacollection( self, request: RasterGridRequest | RotationScanRequest, screening: bool = False ): request_omega = getattr(request, "omega_deg", None) @@ -1955,19 +1949,19 @@ class AareDAQ: if request_omega is not None: logger.info(f"requesting omega to move to {request_omega}") - self.__devs.aerotech_omega = request_omega + self._devs.aerotech_omega = request_omega if request.dtz is not None: - if request.dtz < self.__cfg.cached_dtz_low: + if request.dtz < self._cfg.cached_dtz_low: raise ValueError( - f"Requested DTZ {request.dtz} is less than low detector limit {self.__cfg.cached_dtz_low}" + f"Requested DTZ {request.dtz} is less than low detector limit {self._cfg.cached_dtz_low}" ) - elif request.dtz > self.__cfg.cached_dtz_high: + elif request.dtz > self._cfg.cached_dtz_high: raise ValueError( - f"Requested DTZ {request.dtz} exceeds high detector limit {self.__cfg.cached_dtz_high}" + f"Requested DTZ {request.dtz} exceeds high detector limit {self._cfg.cached_dtz_high}" ) logger.info(f"requesting dtz to move to {request.dtz}") - self.__cfg.dtz = request.dtz + self._cfg.dtz = request.dtz else: raise ValueError("No DTZ specified") @@ -1981,14 +1975,14 @@ class AareDAQ: screenshot_name = f"{sample_id}_before_data_collection" self.save_screenshot_db(sample_id, screenshot_name) - if request.transmission is not None and request.transmission != self.__devs.transmission: + if request.transmission is not None and request.transmission != self._devs.transmission: logger.info(f"requesting transmission to move to {request.transmission}") - self.__devs.transmission = request.transmission + self._devs.transmission = request.transmission start_pos = getattr(request, "start", None) if start_pos is not None: logger.info(f"requesting smargon to move to {start_pos}") - self.__devs.smargon_pos = start_pos + self._devs.smargon_pos = start_pos else: smargon_top_left = getattr(request, "smargon_top_left", None) has_valid_smargon_target = ( @@ -2007,7 +2001,7 @@ class AareDAQ: if has_valid_smargon_target and not is_zero_placeholder: logger.info(f"requesting smargon to move to {smargon_top_left}") - self.__devs.set_smargon_pos( + self._devs.set_smargon_pos( SmargonCoordinate( sh_mm=smargon_top_left.sh_mm, phi_deg=smargon_top_left.phi_deg, @@ -2019,7 +2013,7 @@ class AareDAQ: "No explicit smargon target in request; skipping pre-datacollection smargon move" ) - self.__devs.smargon_wait(timeout=180) + self._devs.smargon_wait(timeout=180) return def _build_fake_rotation_result(self, request: RotationScanRequest) -> CompletedRotationScan: @@ -2048,21 +2042,21 @@ class AareDAQ: sample_log_context(self.sample), raster_request_log_context(request) ), ) - self.__cfg.try_set_busy(timeout=ceil(360)) + self._cfg.try_set_busy(timeout=ceil(360)) try: result = self._execute_raster_sequence(request, auto_center=auto_center) if result is None: raise RasterScanException("Raster scan failed") - self.__set_state(BeamlineStateEnum.SampleAlignment) - self.__cfg.state_busy = False + self._set_state(BeamlineStateEnum.SampleAlignment) + self._cfg.state_busy = False return result except Exception: - self.__set_state(BeamlineStateEnum.SampleAlignment) - self.__cfg.state_busy = False + self._set_state(BeamlineStateEnum.SampleAlignment) + self._cfg.state_busy = False raise - def __rotation(self, request: RotationScanRequest) -> CompletedRotationScan: + def _rotation(self, request: RotationScanRequest) -> CompletedRotationScan: omega_start = self.omega status = self.status @@ -2071,17 +2065,17 @@ class AareDAQ: request.exp_time_s = 0.004 total_time = request.exp_time_s * request.steps if self.sample is not None and self.sample.db_id is not None: - self.__aare.create_rotation_run(self.sample, request, status) + self._aare.create_rotation_run(self.sample, request, status) - if not self.__cfg.simulated_detector: + if not self._cfg.simulated_detector: try: - self.__jfjoch.wait_till_running(timeout=60.0) + self._jfjoch.wait_till_running(timeout=60.0) except Exception as e: self._raise_if_critical_jfjoch_detector_error(e, command="wait_till_running") raise try: if request.screening: - self.__devs.aerotech.screening_scan( + self._devs.aerotech.screening_scan( rotation_deg=request.steps * request.incr_omega_deg, wedge_deg=request.wedge_omega_deg, time_sec=total_time, @@ -2089,7 +2083,7 @@ class AareDAQ: run_async=True, ) else: - self.__devs.aerotech.rotation_scan( + self._devs.aerotech.rotation_scan( rotation_deg=request.steps * request.incr_omega_deg, time_sec=total_time, start_pos_deg=request.start_omega_deg, @@ -2104,20 +2098,20 @@ class AareDAQ: ) for i in range(request.steps): - self.__devs.smargon.target = SmargonCoordinate( + self._devs.smargon.target = SmargonCoordinate( sh_mm=request.start.sh_mm + pos_step * i ) time.sleep(smargon_time_step) - self.__devs.aerotech.wait_till_done(timeout=int(round(total_time + 60, 0))) - self.__devs.aerotech_omega = omega_start + self._devs.aerotech.wait_till_done(timeout=int(round(total_time + 60, 0))) + self._devs.aerotech_omega = omega_start - if self.__cfg.simulated_detector: + if self._cfg.simulated_detector: logger.warning("Detector in simulation mode, returning fake zero rotation result.") return self._build_fake_rotation_result(request) else: try: - scan_result = self.__jfjoch.wait_till_done(60) + scan_result = self._jfjoch.wait_till_done(60) except Exception as e: self._raise_if_critical_jfjoch_detector_error(e, command="wait_till_done") raise @@ -2148,7 +2142,7 @@ class AareDAQ: rotation_request_log_context(request, total_time_s=total_time), ), ) - self.__cfg.try_set_busy(timeout=ceil(total_time + 360)) + self._cfg.try_set_busy(timeout=ceil(total_time + 360)) try: result = self._execute_rotation_sequence(request) @@ -2156,23 +2150,23 @@ class AareDAQ: if result is None: logger.error("Rotation scan failed, no result returned") raise DataCollectionException("Rotation scan failed, no result returned") - self.__set_state(BeamlineStateEnum.SampleAlignment) - self.__cfg.state_busy = False + self._set_state(BeamlineStateEnum.SampleAlignment) + self._cfg.state_busy = False return result finally: try: - if self.__cfg.state_busy and self.__cfg.state != BeamlineStateEnum.Maintenance: - self.__set_state(BeamlineStateEnum.SampleAlignment) + if self._cfg.state_busy and self._cfg.state != BeamlineStateEnum.Maintenance: + self._set_state(BeamlineStateEnum.SampleAlignment) except Exception as cleanup_error: logger.exception( f"Failed to restore SampleAlignment after rotation: {cleanup_error}" ) finally: - self.__cfg.state_busy = False + self._cfg.state_busy = False @property def dtz(self) -> float: - tmp = self.__cfg.dtz + tmp = self._cfg.dtz if tmp is None: return cfg_get("daq.data_collection_settings.default_raster_scan_settings.dtz", 200) else: @@ -2180,81 +2174,81 @@ class AareDAQ: @dtz.setter def dtz(self, val: float): - self.__cfg.try_set_busy(timeout=360) - state = self.__cfg.state + self._cfg.try_set_busy(timeout=360) + state = self._cfg.state - dtz_low = self.__cfg.cached_dtz_low - dtz_high = self.__cfg.cached_dtz_high + dtz_low = self._cfg.cached_dtz_low + dtz_high = self._cfg.cached_dtz_high if dtz_low is None or dtz_high is None: logger.warning("DTZ limits not found in cache, refreshing hardware metadata") try: self.refresh_detector_metadata_cache() except Exception as e: - self.__cfg.state_busy = False + self._cfg.state_busy = False raise RuntimeError(f"DTZ limits unavailable and refresh failed: {e}") from e - dtz_low = self.__cfg.cached_dtz_low - dtz_high = self.__cfg.cached_dtz_high + dtz_low = self._cfg.cached_dtz_low + dtz_high = self._cfg.cached_dtz_high if dtz_low is None or dtz_high is None: - self.__cfg.state_busy = False + self._cfg.state_busy = False raise RuntimeError("DTZ limits are unavailable") if val < dtz_low or val > dtz_high: - self.__cfg.state_busy = False + self._cfg.state_busy = False raise RuntimeError(f"dtz={val} outside limits {dtz_low} to {dtz_high}") if state == BeamlineStateEnum.DataCollection: - self.__cfg.state_busy = False + self._cfg.state_busy = False raise RuntimeError("Cannot set dtz during data collection") elif state == BeamlineStateEnum.SampleAlignment: - self.__devs.set_dtz(val, wait=False) + self._devs.set_dtz(val, wait=False) - self.__cfg.dtz = val - self.__cfg.state_busy = False + self._cfg.dtz = val + self._cfg.state_busy = False @property def smargon(self) -> SmargonCoordinate: - return self.__devs.smargon_pos + return self._devs.smargon_pos @smargon.setter def smargon(self, sc: SmargonCoordinate): - self.__cfg.set_busy(BeamlineStateEnum.SampleAlignment) + self._cfg.set_busy(BeamlineStateEnum.SampleAlignment) try: - self.__saved_box = None - self.__devs.smargon_pos = sc - self.__devs.smargon_wait() - self.__cfg.state_busy = False + self._saved_box = None + self._devs.smargon_pos = sc + self._devs.smargon_wait() + self._cfg.state_busy = False except Exception: - self.__cfg.state_busy = False + self._cfg.state_busy = False raise pass def mark_beam(self, x_pxl: float, y_pxl: float): - self.__cfg.set_busy(BeamlineStateEnum.BeamLocation) + self._cfg.set_busy(BeamlineStateEnum.BeamLocation) try: - self.__cfg.mark_beam(x_pxl, y_pxl, self.__devs.zoom) - self.__cfg.state_busy = False + self._cfg.mark_beam(x_pxl, y_pxl, self._devs.zoom) + self._cfg.state_busy = False except Exception: - self.__cfg.state_busy = False + self._cfg.state_busy = False raise def clear_mark_beam(self): - self.__cfg.set_busy(BeamlineStateEnum.BeamLocation) - self.__cfg.clear_mark_beam() - self.__cfg.state_busy = False + self._cfg.set_busy(BeamlineStateEnum.BeamLocation) + self._cfg.clear_mark_beam() + self._cfg.state_busy = False @property def sample_geometry(self) -> SampleGeometryModel: - zoom = self.__devs.zoom - aerotech_pos_ref = self.__cfg.abr_meas_pos.at_mm - aerotech_pos = self.__devs.aerotech_pos.at_mm + zoom = self._devs.zoom + aerotech_pos_ref = self._cfg.abr_meas_pos.at_mm + aerotech_pos = self._devs.aerotech_pos.at_mm sample_geom = SampleGeometryModel( - beam_location_pxl=self.__cfg.beam_mark_coeff.apply(zoom), - pixel_in_mm=self.__cfg.pixel_to_mm(zoom), - omega_deg=self.__devs.aerotech_omega, - smargon=self.__devs.smargon_pos, - beam_size_mm=self.__cfg.beam_size_mm, + beam_location_pxl=self._cfg.beam_mark_coeff.apply(zoom), + pixel_in_mm=self._cfg.pixel_to_mm(zoom), + omega_deg=self._devs.aerotech_omega, + smargon=self._devs.smargon_pos, + beam_size_mm=self._cfg.beam_size_mm, aerotech=aerotech_pos - aerotech_pos_ref, aerotech_meas=aerotech_pos, ) @@ -2262,22 +2256,22 @@ class AareDAQ: @property def beam_center(self) -> Tuple[float, float]: - return self.__cfg.beam_center + return self._cfg.beam_center @beam_center.setter def beam_center(self, val: Tuple[float, float]): - self.__cfg.beam_center = val + self._cfg.beam_center = val @property def beam_size_mm(self) -> Coordinate: - return self.__cfg.beam_size_mm + return self._cfg.beam_size_mm @beam_size_mm.setter def beam_size_mm(self, val: Coordinate): - self.__cfg.beam_size_mm = val + self._cfg.beam_size_mm = val def get_beam_mark(self): - return self.__cfg.get_beam_mark(self.__devs.zoom) + return self._cfg.get_beam_mark(self._devs.zoom) def ml_bounding_box( self, sample_id: int | None = None, filename: str | None = None @@ -2293,22 +2287,22 @@ class AareDAQ: RasterGridRequest object representing the found bounding box, or None if failed. """ try: - self.__cfg.try_set_busy(timeout=360) + self._cfg.try_set_busy(timeout=360) r = get_ml_bounding_box( - mlbox=self.__mlbox, + mlbox=self._mlbox, sample=self.sample, sample_geometry=self.sample_geometry, filename=filename, - upload_image=self.__aare.upload_image, + upload_image=self._aare.upload_image, logger=logger, max_images=self.AUTO_RASTER_MAX_IMAGES, min_cell_size_mm=self.AUTO_RASTER_MAX_IMAGES, skip_if_exceed_max_image_threshold=self.AUTO_RASTER_SKIP_IF_EXCEED_MAX_IMAGE_THRESHOLD, ) - self.__cfg.state_busy = False + self._cfg.state_busy = False return r except Exception: - self.__cfg.state_busy = False + self._cfg.state_busy = False raise def face_detection( @@ -2325,14 +2319,14 @@ class AareDAQ: Returns: Dictionary containing face detection results, including found samples and fits. """ - self.__cfg.try_set_busy(timeout=360) + self._cfg.try_set_busy(timeout=360) try: result = self._execute_face_detection( steps=steps, step_size=step_size, face_min_ratio=face_min_ratio, report_error=True ) return result.payload finally: - self.__cfg.state_busy = False + self._cfg.state_busy = False @log_timing(logger, "Auto loop center") def auto_loop_center(self, sample: Optional[SampleShortInfo] = None) -> float: @@ -2351,7 +2345,7 @@ class AareDAQ: """ start = time.perf_counter() try: - self.__cfg.try_set_busy(timeout=360) + self._cfg.try_set_busy(timeout=360) if sample is None: if self.sample is None: raise LoopCenteringFailed( @@ -2364,15 +2358,15 @@ class AareDAQ: if not self._execute_loop_centering(sample): raise LoopCenteringFailed - self.__cfg.state_busy = False + self._cfg.state_busy = False except Exception: - self.__cfg.zoom_mode = ZoomModeEnum.User - self.__cfg.state_busy = False + self._cfg.zoom_mode = ZoomModeEnum.User + self._cfg.state_busy = False raise finally: - self.__cfg.zoom_mode = ZoomModeEnum.User + self._cfg.zoom_mode = ZoomModeEnum.User end = time.perf_counter() return end - start @@ -2424,15 +2418,15 @@ class AareDAQ: def send_message_db( self, db_id: int, event_type: SampleEventType, comment: Optional[str] = None ): - self.__aare.send_sample_event(db_id, event_type, comment) + self._aare.send_sample_event(db_id, event_type, comment) @property def sample_spreadsheet(self) -> SampleShortInfoList: - return self.__cfg.spreadsheet + return self._cfg.spreadsheet @property def reference_tools(self) -> SampleShortInfoList: - return self.__cfg.reference_tools + return self._cfg.reference_tools def sample_spreadsheet_user(self, pgroup: str) -> SampleShortInfoList: # sample = copy.deepcopy(self.sample_spreadsheet) #switch to copy if too heavy! @@ -2532,7 +2526,7 @@ class AareDAQ: def get_collection_params(self, prefer_smart: bool = False) -> tuple[SimpleScanParameters, str]: spreadsheet_params, file_prefix = self.spreadsheet_params() logger.debug(f"spreadsheet_params: {spreadsheet_params}") - smart_params = self.__cfg.auto_params + smart_params = self._cfg.auto_params default_params = SimpleScanParameters(exp_time_s=0.04, dtz=110, incr_omega_deg=0.2) if prefer_smart: if smart_params: @@ -2568,8 +2562,8 @@ class AareDAQ: msg += "with an error" logger.error(f"{msg}, time taken {time.perf_counter() - start} seconds.") try: - if self.__cfg.state_busy: - self.__set_state(BeamlineStateEnum.RobotSampleExchange) + if self._cfg.state_busy: + self._set_state(BeamlineStateEnum.RobotSampleExchange) else: logger.warning( "Skipping recovery transition to RobotSampleExchange: " @@ -2584,7 +2578,7 @@ class AareDAQ: else: msg += " successfully" logger.info(f"{msg}, time taken {time.perf_counter() - start} seconds.") - self.__cfg.state_busy = False + self._cfg.state_busy = False end = time.perf_counter() return end - start @@ -2615,11 +2609,11 @@ class AareDAQ: try: self._validate_automation_state(context="automation start") - self.__cfg.try_set_busy(timeout=self.AUTOMATION_BUSY_TIMEOUT_S) + self._cfg.try_set_busy(timeout=self.AUTOMATION_BUSY_TIMEOUT_S) self._validate_automation_state(context="after acquiring automation busy state") logger.info("Cancelling any pending jfjoch operations") - self.__jfjoch.cancel() + self._jfjoch.cancel() self._set_progress_context( progress, current_sample_name=getattr(sample, "sample_name", "") or "" ) @@ -2631,7 +2625,7 @@ class AareDAQ: return self._end_operation(start, DAQOperation.MOUNT, error=True) self._validate_automation_state(context="after mount") self._mark_progress_success(progress, WorkflowStateKind.MOUNT, "Mount complete") - self.__set_state(BeamlineStateEnum.SampleAlignment) + self._set_state(BeamlineStateEnum.SampleAlignment) self._validate_automation_state(context="after transition to SampleAlignment") logger.info(f"mounting done at {time.perf_counter() - start}") @@ -2756,7 +2750,7 @@ class AareDAQ: self._validate_automation_state(context="after raster") self._mark_progress_success(progress, WorkflowStateKind.RASTER, "Raster complete") - self.__set_state(BeamlineStateEnum.DataCollection) + self._set_state(BeamlineStateEnum.DataCollection) self._validate_automation_state(context="after transition to DataCollection") logger.info(f"Raster scans completed at {time.perf_counter() - start}") @@ -2767,7 +2761,7 @@ class AareDAQ: params, source = self.get_collection_params(prefer_smart=False) logger.info(f"Using {source} for data collection: {params}") - self.__devs.dtz = params.dtz + self._devs.dtz = params.dtz start_omega = self.omega update_sample = self.sample @@ -2922,7 +2916,7 @@ class AareDAQ: return self._end_operation(start, DAQOperation.AUTOMATION, error=False) @log_timing(logger, "Changing Beamline State") - def __set_state(self, target: BeamlineStateEnum): + def _set_state(self, target: BeamlineStateEnum): """__set_state assumes that beamline is already in busy state it will apply a proper transformation and change state afterward specifically: @@ -2934,7 +2928,7 @@ class AareDAQ: Busy state will be cleared only, if exception is raised.""" - curr_state = self.__cfg.state + curr_state = self._cfg.state if hasattr(curr_state, "value") and hasattr(target, "value"): logger.info( @@ -2942,11 +2936,11 @@ class AareDAQ: extra={"from_state": curr_state, "to_state": target}, ) - if not self.__cfg.state_busy: + if not self._cfg.state_busy: raise Exception("Beamline should be busy") if target == BeamlineStateEnum.Maintenance: - self.__cfg.state = BeamlineStateEnum.Maintenance + self._cfg.state = BeamlineStateEnum.Maintenance logger.warning("Beamline entered Maintenance state during state transition request.") return elif target == curr_state: @@ -2958,172 +2952,172 @@ class AareDAQ: # TODO or just allow BEC thingy return elif target != curr_state: - self.__cfg.state = BeamlineStateEnum.Moving + self._cfg.state = BeamlineStateEnum.Moving try: match curr_state: case BeamlineStateEnum.Maintenance: if target == BeamlineStateEnum.SampleExchange: - workflows.m2se(self.__devs, self.__cfg) + workflows.m2se(self._devs, self._cfg) elif target == BeamlineStateEnum.SampleAlignment: - workflows.m2sa(self.__devs, self.__cfg) + workflows.m2sa(self._devs, self._cfg) else: raise TransformationInvalidException( f"Cannot go from {curr_state} to {target}, not implemented" ) case BeamlineStateEnum.SampleExchange: if target == BeamlineStateEnum.SampleAlignment: - workflows.se2sa(self.__devs, self.__cfg) + workflows.se2sa(self._devs, self._cfg) elif target == BeamlineStateEnum.BeamLocation: - workflows.se2sa(self.__devs, self.__cfg) - workflows.sa2bl(self.__devs, self.__cfg) + workflows.se2sa(self._devs, self._cfg) + workflows.sa2bl(self._devs, self._cfg) elif target == BeamlineStateEnum.DewarTransfer: - workflows.common2dh(self.__devs, self.__cfg) + workflows.common2dh(self._devs, self._cfg) elif target == BeamlineStateEnum.RobotSampleExchange: - workflows.common_2rse(self.__devs, self.__cfg) + workflows.common_2rse(self._devs, self._cfg) else: raise TransformationInvalidException( f"Cannot go from {curr_state} to {target}, not implemented" ) case BeamlineStateEnum.DewarTransfer: if target == BeamlineStateEnum.SampleAlignment: - workflows.dh2sa(self.__devs, self.__cfg) + workflows.dh2sa(self._devs, self._cfg) elif target == BeamlineStateEnum.RobotSampleExchange: - workflows.dh2sa(self.__devs, self.__cfg) - workflows.sa2rse(self.__devs, self.__cfg) + workflows.dh2sa(self._devs, self._cfg) + workflows.sa2rse(self._devs, self._cfg) elif target == BeamlineStateEnum.SampleExchange: - workflows.dh2sa(self.__devs, self.__cfg) - workflows.sa2se(self.__devs, self.__cfg) + workflows.dh2sa(self._devs, self._cfg) + workflows.sa2se(self._devs, self._cfg) else: raise TransformationInvalidException( f"Cannot go from {curr_state} to {target}, not implemented" ) case BeamlineStateEnum.DataCollection: if target == BeamlineStateEnum.SampleAlignment: - workflows.dc2sa(self.__devs, self.__cfg) + workflows.dc2sa(self._devs, self._cfg) elif target == BeamlineStateEnum.RobotSampleExchange: - workflows.dc2rse(self.__devs, self.__cfg) + workflows.dc2rse(self._devs, self._cfg) elif target == BeamlineStateEnum.XtalSnapshot: - workflows.sa2xtal_snapshot(self.__devs, self.__cfg) + workflows.sa2xtal_snapshot(self._devs, self._cfg) else: raise TransformationInvalidException( f"Cannot go from {curr_state} to {target}, not implemented" ) case BeamlineStateEnum.BeamLocation: if target == BeamlineStateEnum.SampleAlignment: - workflows.bl2sa(self.__devs, self.__cfg) + workflows.bl2sa(self._devs, self._cfg) elif target == BeamlineStateEnum.SampleExchange: - workflows.bl2sa(self.__devs, self.__cfg) - workflows.sa2se(self.__devs, self.__cfg) + workflows.bl2sa(self._devs, self._cfg) + workflows.sa2se(self._devs, self._cfg) elif target == BeamlineStateEnum.BeamstopAlignment: - workflows.bl2ba(self.__devs, self.__cfg) + workflows.bl2ba(self._devs, self._cfg) elif target == BeamlineStateEnum.FluxMeasurement: - workflows.bl2flux_measurement(self.__devs, self.__cfg) + workflows.bl2flux_measurement(self._devs, self._cfg) else: raise TransformationInvalidException( f"Cannot go from {curr_state} to {target}, not implemented" ) case BeamlineStateEnum.BeamstopAlignment: if target == BeamlineStateEnum.SampleAlignment: - workflows.ba2sa(self.__devs, self.__cfg) + workflows.ba2sa(self._devs, self._cfg) elif target == BeamlineStateEnum.SampleExchange: - workflows.ba2sa(self.__devs, self.__cfg) - workflows.sa2se(self.__devs, self.__cfg) + workflows.ba2sa(self._devs, self._cfg) + workflows.sa2se(self._devs, self._cfg) elif target == BeamlineStateEnum.FluxMeasurement: - workflows.ba2flux_measurement(self.__devs, self.__cfg) + workflows.ba2flux_measurement(self._devs, self._cfg) elif target == BeamlineStateEnum.BeamLocation: - workflows.ba2bl(self.__devs, self.__cfg) + workflows.ba2bl(self._devs, self._cfg) elif target == BeamlineStateEnum.SampleExchange: - workflows.bl2sa(self.__devs, self.__cfg) - workflows.sa2se(self.__devs, self.__cfg) + workflows.bl2sa(self._devs, self._cfg) + workflows.sa2se(self._devs, self._cfg) else: raise TransformationInvalidException( f"Cannot go from {curr_state} to {target}, not implemented" ) case BeamlineStateEnum.FluxMeasurement: if target == BeamlineStateEnum.SampleAlignment: - workflows.flux_measurement2sa(self.__devs, self.__cfg) + workflows.flux_measurement2sa(self._devs, self._cfg) elif target == BeamlineStateEnum.SampleExchange: - workflows.flux_measurement2sa(self.__devs, self.__cfg) - workflows.sa2se(self.__devs, self.__cfg) + workflows.flux_measurement2sa(self._devs, self._cfg) + workflows.sa2se(self._devs, self._cfg) elif target == BeamlineStateEnum.BeamLocation: - workflows.flux_measurement2bl(self.__devs, self.__cfg) + workflows.flux_measurement2bl(self._devs, self._cfg) elif target == BeamlineStateEnum.BeamstopAlignment: - workflows.flux_measurement2ba(self.__devs, self.__cfg) + workflows.flux_measurement2ba(self._devs, self._cfg) else: raise TransformationInvalidException( f"Cannot go from {curr_state} to {target}, not implemented" ) case BeamlineStateEnum.SampleAlignment: if target == BeamlineStateEnum.DewarTransfer: - workflows.sa2dh(self.__devs, self.__cfg) + workflows.sa2dh(self._devs, self._cfg) elif target == BeamlineStateEnum.SampleExchange: - workflows.sa2se(self.__devs, self.__cfg) + workflows.sa2se(self._devs, self._cfg) elif target == BeamlineStateEnum.RobotSampleExchange: - workflows.sa2rse(self.__devs, self.__cfg) + workflows.sa2rse(self._devs, self._cfg) elif target == BeamlineStateEnum.DataCollection: - workflows.sa2dc(self.__devs, self.__cfg) + workflows.sa2dc(self._devs, self._cfg) elif target == BeamlineStateEnum.XrayFluorescence: - workflows.sa2xrf(self.__devs, self.__cfg) + workflows.sa2xrf(self._devs, self._cfg) elif target == BeamlineStateEnum.BeamLocation: - workflows.sa2bl(self.__devs, self.__cfg) + workflows.sa2bl(self._devs, self._cfg) elif target == BeamlineStateEnum.BeamstopAlignment: - workflows.sa2ba(self.__devs, self.__cfg) + workflows.sa2ba(self._devs, self._cfg) elif target == BeamlineStateEnum.FluxMeasurement: - workflows.sa2flux_measurement(self.__devs, self.__cfg) + workflows.sa2flux_measurement(self._devs, self._cfg) elif target == BeamlineStateEnum.XtalSnapshot: - workflows.sa2xtal_snapshot(self.__devs, self.__cfg) + workflows.sa2xtal_snapshot(self._devs, self._cfg) else: raise TransformationInvalidException( f"Cannot go from {curr_state} to {target}, not implemented" ) case BeamlineStateEnum.XrayFluorescence: if target == BeamlineStateEnum.SampleAlignment: - workflows.xrf2sa(self.__devs, self.__cfg) + workflows.xrf2sa(self._devs, self._cfg) else: raise TransformationInvalidException( f"Cannot go from {curr_state} to {target}, not implemented" ) case BeamlineStateEnum.RobotSampleExchange: if target == BeamlineStateEnum.SampleAlignment: - workflows.rse2sa(self.__devs, self.__cfg) + workflows.rse2sa(self._devs, self._cfg) elif target == BeamlineStateEnum.SampleExchange: - workflows.rse2se(self.__devs, self.__cfg) + workflows.rse2se(self._devs, self._cfg) elif target == BeamlineStateEnum.DewarTransfer: - workflows.common2dh(self.__devs, self.__cfg) + workflows.common2dh(self._devs, self._cfg) else: raise TransformationInvalidException( f"Cannot go from {curr_state} to {target}, not implemented" ) case BeamlineStateEnum.XtalSnapshot: if target == BeamlineStateEnum.SampleAlignment: - workflows.xtal_snapshot2sa(self.__devs, self.__cfg) + workflows.xtal_snapshot2sa(self._devs, self._cfg) elif target == BeamlineStateEnum.SampleExchange: - workflows.xtal_snapshot2se(self.__devs, self.__cfg) + workflows.xtal_snapshot2se(self._devs, self._cfg) elif target == BeamlineStateEnum.RobotSampleExchange: - workflows.xtal_snapshot2rse(self.__devs, self.__cfg) + workflows.xtal_snapshot2rse(self._devs, self._cfg) elif target == BeamlineStateEnum.DataCollection: - workflows.xtal_snapshot2dc(self.__devs, self.__cfg) + workflows.xtal_snapshot2dc(self._devs, self._cfg) elif target == BeamlineStateEnum.XrayFluorescence: - workflows.xtal_snapshot2xrf(self.__devs, self.__cfg) + workflows.xtal_snapshot2xrf(self._devs, self._cfg) elif target == BeamlineStateEnum.DewarTransfer: - workflows.xtal_snapshot2dh(self.__devs, self.__cfg) + workflows.xtal_snapshot2dh(self._devs, self._cfg) elif target == BeamlineStateEnum.BeamLocation: - workflows.xtal_snapshot2bl(self.__devs, self.__cfg) + workflows.xtal_snapshot2bl(self._devs, self._cfg) else: raise TransformationInvalidException( f"Cannot go from {curr_state} to {target}, not implemented" ) - self.__cfg.state = target + self._cfg.state = target # Beam-location mode uses preset per-zoom camera settings; any # other state goes back to auto-exposure (User zoom mode). Apply # the preset for the current zoom on entry. if target == BeamlineStateEnum.BeamLocation: - self.__cfg.zoom_mode = ZoomModeEnum.BeamLocation - self.__devs.samcam_settings = self.__cfg.zoom_settings.get_camera_settings( - self.__devs.zoom + self._cfg.zoom_mode = ZoomModeEnum.BeamLocation + self._devs.samcam_settings = self._cfg.zoom_settings.get_camera_settings( + self._devs.zoom ) - elif self.__cfg.zoom_mode == ZoomModeEnum.BeamLocation: - self.__cfg.zoom_mode = ZoomModeEnum.User + elif self._cfg.zoom_mode == ZoomModeEnum.BeamLocation: + self._cfg.zoom_mode = ZoomModeEnum.User logger.info( f"State transition completed: {curr_state} -> {target}", extra={"from_state": curr_state, "to_state": target}, @@ -3133,7 +3127,7 @@ class AareDAQ: f"State transition invali: {curr_state} -> {target}: {e}", extra={"from_state": curr_state, "to_state": target}, ) - self.__cfg.state = curr_state + self._cfg.state = curr_state raise except Exception as e: logger.exception( @@ -3141,8 +3135,8 @@ class AareDAQ: f"Changing state to maintenance due to error: {e}", extra={"from_state": curr_state, "to_state": target}, ) - self.__cfg.state = BeamlineStateEnum.Maintenance - self.__cfg.state_busy = False + self._cfg.state = BeamlineStateEnum.Maintenance + self._cfg.state_busy = False raise StateTransitionFailed( f"State transition failed: {curr_state} -> {target}. " f"Beamline moved to Maintenance. Original error: {e}" @@ -3150,11 +3144,11 @@ class AareDAQ: @property def shutter(self) -> bool: - return self.__devs.shutter + return self._devs.shutter @shutter.setter def shutter(self, v: bool): - self.__devs.shutter = v + self._devs.shutter = v @property def diffraction_geometry(self) -> DiffractionGeometry: @@ -3165,9 +3159,9 @@ class AareDAQ: pixel_size_mm = float(metadata.get("pixel_size_mm", 0.15)) detector_description = str(metadata.get("detector_description", "unavailable")) detector_serial_number = str(metadata.get("detector_serial_number", "unavailable")) - energy = self.__devs.energy_kev - dtz = self.__devs.dtz - beam_center = self.__cfg.beam_center + energy = self._devs.energy_kev + dtz = self._devs.dtz + beam_center = self._cfg.beam_center return DiffractionGeometry( energy_keV=energy, dtz_mm=dtz, @@ -3183,9 +3177,9 @@ class AareDAQ: logger.warning( f"Falling back to default diffraction geometry because cached detector metadata is unavailable: {e}" ) - energy = self.__devs.energy_kev - dtz = self.__devs.dtz - beam_center = self.__cfg.beam_center + energy = self._devs.energy_kev + dtz = self._devs.dtz + beam_center = self._cfg.beam_center return DiffractionGeometry( energy_keV=energy, dtz_mm=dtz, @@ -3201,23 +3195,23 @@ class AareDAQ: @property def beamline_status(self) -> BeamlineStatus: try: - ring_current = self.__devs.ring_current + ring_current = self._devs.ring_current front_light = self.front_light back_light = self.back_light - cryojet_temp = self.__devs.cryojet_temp - shutter_open = self.__devs.shutter - exp_shutter_open = self.__devs.exp_shutter.state() - pss_prohibited = self.__devs.pss.is_prohibited() - pss_alarm = self.__devs.pss.alarm_active() - flux = self.__devs.full_flux - samcam_settings = self.__devs.samcam_settings - bl = self.__bl - transmission = self.__devs.transmission - zoom = self.__devs.zoom - commisioning_mode = self.__cfg.commissioning_mode + cryojet_temp = self._devs.cryojet_temp + shutter_open = self._devs.shutter + exp_shutter_open = self._devs.exp_shutter.state() + pss_prohibited = self._devs.pss.is_prohibited() + pss_alarm = self._devs.pss.alarm_active() + flux = self._devs.full_flux + samcam_settings = self._devs.samcam_settings + bl = self._bl + transmission = self._devs.transmission + zoom = self._devs.zoom + commisioning_mode = self._cfg.commissioning_mode - dtz_min = self.__cfg.cached_dtz_low - dtz_max = self.__cfg.cached_dtz_high + dtz_min = self._cfg.cached_dtz_low + dtz_max = self._cfg.cached_dtz_high if ( dtz_min is None @@ -3263,17 +3257,17 @@ class AareDAQ: try: return self.sync_current_sample_from_tell(), True, None except TellCommunicationError as e: - return self.__cfg.current_sample, False, str(e) + return self._cfg.current_sample, False, str(e) except Exception as e: logger.warning(f"error in status sample info call: {e}") # Keep status flowing even if Tell code throws something unexpected - return self.__cfg.current_sample, False, f"TELL unavailable: {e}" + return self._cfg.current_sample, False, f"TELL unavailable: {e}" def _aerotech_status(self) -> tuple[bool, str | None]: aerotech_ok = True aerotech_err: str | None = None try: - _ = self.__devs.aerotech.status() + _ = self._devs.aerotech.status() except Exception as e: logger.warning(f"Aerotech error in _aerotech_status: {e}") aerotech_ok = False @@ -3302,7 +3296,7 @@ class AareDAQ: except AerotechCommunicationError as e: logger.warning(f"Aeroetch error in _safe_geom: {e}") aerotech_error = f"Cannot connect to Aerotech: {e}" - self.__devs.exp_shutter.close() + self._devs.exp_shutter.close() aerotech_connected = False except Exception as e: logger.warning(f"Unexpected error in _safe_geom: {e}") @@ -3310,16 +3304,16 @@ class AareDAQ: aerotech_error = f"Safe geometry failed: {e}" aerotech_connected = False smargon_connected = False - zoom = self.__devs.zoom + zoom = self._devs.zoom logger.warning("Safe geometry failed: falling back to default settings") fallback = SampleGeometryModel( - beam_location_pxl=self.__cfg.beam_mark_coeff.apply(zoom), - pixel_in_mm=self.__cfg.pixel_to_mm(zoom), + beam_location_pxl=self._cfg.beam_mark_coeff.apply(zoom), + pixel_in_mm=self._cfg.pixel_to_mm(zoom), omega_deg=0.0, smargon=SmargonCoordinate( sh_mm=Coordinate(x=0.0, y=0.0, z=0.0), phi_deg=0.0, chi_deg=0.0 ), - beam_size_mm=self.__cfg.beam_size_mm, + beam_size_mm=self._cfg.beam_size_mm, aerotech=Coordinate(x=0.0, y=0.0, z=0.0), aerotech_meas=Coordinate(x=0.0, y=0.0, z=0.0), ) @@ -3335,8 +3329,8 @@ class AareDAQ: def _safe_tell_state(self) -> TellStateModel | None: try: - redis_client = getattr(self._AareDAQ__cfg, "_BeamlineConfig__client", None) - beamline_key = getattr(self._AareDAQ__cfg, "_BeamlineConfig__bl", None) + redis_client = getattr(self._cfg, "_client", None) + beamline_key = getattr(self._cfg, "_bl", None) if redis_client is None or beamline_key is None: return None @@ -3392,8 +3386,8 @@ class AareDAQ: # logger.debug(f"Safe diffraction geometry call took {time.perf_counter() - start:.3f}s") # start = time.perf_counter() session_status = SessionStatus( - current_pgroup=self.__cfg.pgroup, - session=self.__cfg.session_state(0), # 0 is dummy session + current_pgroup=self._cfg.pgroup, + session=self._cfg.session_state(0), # 0 is dummy session staff=False, ) # logger.debug(f"Safe session status call took {time.perf_counter() - start:.3f}s") @@ -3406,10 +3400,10 @@ class AareDAQ: sample=safe_sample, session=session_status, diffraction=safe_diffraction_geom, - box=self.__saved_box, - last_best_res=self.__cfg.last_best_res, - last_best_b_factor=self.__cfg.last_best_b_factor, - crystal_size=self.__cfg.crystal_size, + box=self._saved_box, + last_best_res=self._cfg.last_best_res, + last_best_b_factor=self._cfg.last_best_b_factor, + crystal_size=self._cfg.crystal_size, tell_connected=tell_ok, tell_error=tell_err, tell_state=safe_tell_state, @@ -3427,102 +3421,102 @@ class AareDAQ: raise def cancel(self): - if self.__cfg.state == BeamlineStateEnum.DataCollection: - self.__devs.aerotech.cancel() - self.__jfjoch.cancel() + if self._cfg.state == BeamlineStateEnum.DataCollection: + self._devs.aerotech.cancel() + self._jfjoch.cancel() def anneal(self, time_s: float): - self.__cfg.set_busy(BeamlineStateEnum.SampleAlignment) + self._cfg.set_busy(BeamlineStateEnum.SampleAlignment) try: - self.__devs.anneal(time_s) + self._devs.anneal(time_s) finally: - self.__cfg.state_busy = False + self._cfg.state_busy = False def mono_pitch_scan(self, plot: bool = False) -> None: - self.__cfg.try_set_busy(timeout=360) + self._cfg.try_set_busy(timeout=360) try: - self.__devs.bec_worker.mono_pitch_scan_runner(plot=plot) + self._devs.bec_worker.mono_pitch_scan_runner(plot=plot) finally: - self.__cfg.state_busy = False + self._cfg.state_busy = False def change_energy(self, value: float, plot: bool = False) -> None: - self.__cfg.try_set_busy(timeout=360) + self._cfg.try_set_busy(timeout=360) try: - self.__devs.bec_worker.change_energy(value=value, plot=plot) + self._devs.bec_worker.change_energy(value=value, plot=plot) finally: - self.__cfg.state_busy = False + self._cfg.state_busy = False def bec_load_user_macros(self) -> None: - self.__cfg.try_set_busy(timeout=360) + self._cfg.try_set_busy(timeout=360) try: - self.__devs.bec_worker.load_user_macros() + self._devs.bec_worker.load_user_macros() finally: - self.__cfg.state_busy = False + self._cfg.state_busy = False def bec_list_all_user_macros(self) -> list[str]: - macros = self.__devs.bec_worker.list_all_user_macros() + macros = self._devs.bec_worker.list_all_user_macros() if macros is None: return [] return [str(item) for item in macros] def bec_list_all_devices(self) -> list[str]: - devices = self.__devs.bec_worker.list_position_devices() + devices = self._devs.bec_worker.list_position_devices() if devices is None: return [] return [str(item) for item in devices] def bec_reinitialise_planner_and_position_devices(self, method: str = "auto") -> list[str]: - self.__cfg.try_set_busy(timeout=360) + self._cfg.try_set_busy(timeout=360) try: - self.__devs.bec_worker.load_user_macros() - return self.__devs.bec_worker.reinitialise_planner_and_position_devices(method=method) + self._devs.bec_worker.load_user_macros() + return self._devs.bec_worker.reinitialise_planner_and_position_devices(method=method) finally: - self.__cfg.state_busy = False + self._cfg.state_busy = False def bec_save_current_bs_pos(self) -> None: - self.__cfg.try_set_busy(timeout=360) + self._cfg.try_set_busy(timeout=360) try: - self.__devs.bec_worker.save_current_bs_pos() + self._devs.bec_worker.save_current_bs_pos() finally: - self.__cfg.state_busy = False + self._cfg.state_busy = False def bec_save_current_collimator_pos(self) -> None: - self.__cfg.try_set_busy(timeout=360) + self._cfg.try_set_busy(timeout=360) try: - self.__devs.bec_worker.save_current_collimator_pos() + self._devs.bec_worker.save_current_collimator_pos() finally: - self.__cfg.state_busy = False + self._cfg.state_busy = False def bec_save_current_aerotech_position(self) -> None: - self.__cfg.try_set_busy(timeout=360) + self._cfg.try_set_busy(timeout=360) try: - self.__devs.bec_worker.save_current_aerotech_position() + self._devs.bec_worker.save_current_aerotech_position() finally: - self.__cfg.state_busy = False + self._cfg.state_busy = False def fluorimeter_take_spectrum( self, fm: FluorescenceSpectrumParameterModel ) -> FluorescenceSpectrumOutputModel: - self.__cfg.try_set_busy(timeout=360) + self._cfg.try_set_busy(timeout=360) try: - self.__set_state(BeamlineStateEnum.XrayFluorescence) + self._set_state(BeamlineStateEnum.XrayFluorescence) if fm.transmission is not None: - self.__devs.transmission = fm.transmission + self._devs.transmission = fm.transmission # TODO: Fill - self.__set_state(BeamlineStateEnum.SampleAlignment) - self.__cfg.state_busy = False + self._set_state(BeamlineStateEnum.SampleAlignment) + self._cfg.state_busy = False return None except Exception: - self.__set_state(BeamlineStateEnum.SampleAlignment) - self.__cfg.state_busy = False + self._set_state(BeamlineStateEnum.SampleAlignment) + self._cfg.state_busy = False raise def get_local_contact_config(self) -> LocalContactConfigModel: - return self.__cfg.get_local_contact_config() + return self._cfg.get_local_contact_config() def set_local_contact_config(self, config: LocalContactConfigModel) -> LocalContactConfigModel: - return self.__cfg.set_local_contact_config(config) + return self._cfg.set_local_contact_config(config) diff --git a/src/aare/daq/devices.py b/src/aare/daq/devices.py index 27a95c8f..d3ee4055 100644 --- a/src/aare/daq/devices.py +++ b/src/aare/daq/devices.py @@ -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__": diff --git a/src/aare/daq/mlbox.py b/src/aare/daq/mlbox.py index b612bd4d..7fdb30fc 100644 --- a/src/aare/daq/mlbox.py +++ b/src/aare/daq/mlbox.py @@ -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: diff --git a/src/aare/daq/server.py b/src/aare/daq/server.py index 068304a6..9a305cdf 100644 --- a/src/aare/daq/server.py +++ b/src/aare/daq/server.py @@ -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) diff --git a/src/aare/daq/spreadsheetupdater.py b/src/aare/daq/spreadsheetupdater.py index f517286b..2c8a86e9 100644 --- a/src/aare/daq/spreadsheetupdater.py +++ b/src/aare/daq/spreadsheetupdater.py @@ -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 diff --git a/src/aare/daq/tellupdater.py b/src/aare/daq/tellupdater.py index 59975a63..d459d203 100644 --- a/src/aare/daq/tellupdater.py +++ b/src/aare/daq/tellupdater.py @@ -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 diff --git a/src/aare/devices/aerotech.py b/src/aare/devices/aerotech.py index 9209fe69..5970f5ad 100644 --- a/src/aare/devices/aerotech.py +++ b/src/aare/devices/aerotech.py @@ -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 diff --git a/src/aare/devices/area_detector.py b/src/aare/devices/area_detector.py index e9c9be0a..b5104ed2 100644 --- a/src/aare/devices/area_detector.py +++ b/src/aare/devices/area_detector.py @@ -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) diff --git a/src/aare/devices/bec_worker.py b/src/aare/devices/bec_worker.py index 8aa5940c..f6649bbe 100644 --- a/src/aare/devices/bec_worker.py +++ b/src/aare/devices/bec_worker.py @@ -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: diff --git a/src/aare/devices/experimental_hutch_shutter.py b/src/aare/devices/experimental_hutch_shutter.py index c72541dd..1510c9b3 100644 --- a/src/aare/devices/experimental_hutch_shutter.py +++ b/src/aare/devices/experimental_hutch_shutter.py @@ -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) diff --git a/src/aare/devices/filter_transmission.py b/src/aare/devices/filter_transmission.py index 36eb9140..2eadb4c0 100644 --- a/src/aare/devices/filter_transmission.py +++ b/src/aare/devices/filter_transmission.py @@ -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"" + return f"" 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 diff --git a/src/aare/devices/fluorimeter.py b/src/aare/devices/fluorimeter.py index 21e1b32f..13faa3e5 100644 --- a/src/aare/devices/fluorimeter.py +++ b/src/aare/devices/fluorimeter.py @@ -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) diff --git a/src/aare/devices/jfjoch.py b/src/aare/devices/jfjoch.py index 5b801f67..d4ecf250 100644 --- a/src/aare/devices/jfjoch.py +++ b/src/aare/devices/jfjoch.py @@ -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, diff --git a/src/aare/devices/pss_state.py b/src/aare/devices/pss_state.py index 907f78ea..c00e608d 100644 --- a/src/aare/devices/pss_state.py +++ b/src/aare/devices/pss_state.py @@ -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): diff --git a/src/aare/devices/smargon.py b/src/aare/devices/smargon.py index 5b4f2af8..e870e277 100644 --- a/src/aare/devices/smargon.py +++ b/src/aare/devices/smargon.py @@ -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 = "" diff --git a/src/aare/devices/tell_client.py b/src/aare/devices/tell_client.py index 14a108e7..cf9ad4d8 100644 --- a/src/aare/devices/tell_client.py +++ b/src/aare/devices/tell_client.py @@ -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 diff --git a/src/aare/devices/zmq_client.py b/src/aare/devices/zmq_client.py index a627d2a4..2b4db0d9 100755 --- a/src/aare/devices/zmq_client.py +++ b/src/aare/devices/zmq_client.py @@ -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" diff --git a/src/aare/gui/main_window.py b/src/aare/gui/main_window.py index ce4eb675..d6825979 100644 --- a/src/aare/gui/main_window.py +++ b/src/aare/gui/main_window.py @@ -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}") diff --git a/src/aare/gui/models/sample_queue_model.py b/src/aare/gui/models/sample_queue_model.py index 47dd35d5..3ceedc1a 100644 --- a/src/aare/gui/models/sample_queue_model.py +++ b/src/aare/gui/models/sample_queue_model.py @@ -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): diff --git a/src/aare/gui/models/user_sample_model.py b/src/aare/gui/models/user_sample_model.py index 95e8d863..9e410552 100644 --- a/src/aare/gui/models/user_sample_model.py +++ b/src/aare/gui/models/user_sample_model.py @@ -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() diff --git a/src/aare/gui/panels/abr_tweak_panel.py b/src/aare/gui/panels/abr_tweak_panel.py index 497d886f..573a9bdf 100644 --- a/src/aare/gui/panels/abr_tweak_panel.py +++ b/src/aare/gui/panels/abr_tweak_panel.py @@ -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);") diff --git a/src/aare/gui/panels/data_collection_settings.py b/src/aare/gui/panels/data_collection_settings.py index 6a65fd1b..40a0b6b8 100644 --- a/src/aare/gui/panels/data_collection_settings.py +++ b/src/aare/gui/panels/data_collection_settings.py @@ -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): diff --git a/src/aare/gui/panels/file_path_panel.py b/src/aare/gui/panels/file_path_panel.py index 9cca0e56..08f27d11 100644 --- a/src/aare/gui/panels/file_path_panel.py +++ b/src/aare/gui/panels/file_path_panel.py @@ -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: diff --git a/src/aare/gui/panels/manual_sample_panel.py b/src/aare/gui/panels/manual_sample_panel.py index 873045b2..9b071140 100644 --- a/src/aare/gui/panels/manual_sample_panel.py +++ b/src/aare/gui/panels/manual_sample_panel.py @@ -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 diff --git a/src/aare/gui/panels/omega_panel.py b/src/aare/gui/panels/omega_panel.py index 2c54fe53..e2585af8 100644 --- a/src/aare/gui/panels/omega_panel.py +++ b/src/aare/gui/panels/omega_panel.py @@ -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 diff --git a/src/aare/gui/panels/portrait_mode.py b/src/aare/gui/panels/portrait_mode.py index 43adf8f7..f3ef9d4d 100644 --- a/src/aare/gui/panels/portrait_mode.py +++ b/src/aare/gui/panels/portrait_mode.py @@ -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 diff --git a/src/aare/gui/panels/raster_data_collection.py b/src/aare/gui/panels/raster_data_collection.py index 75dfca2b..3250f149 100644 --- a/src/aare/gui/panels/raster_data_collection.py +++ b/src/aare/gui/panels/raster_data_collection.py @@ -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"): diff --git a/src/aare/gui/panels/reference_tools_panel.py b/src/aare/gui/panels/reference_tools_panel.py index c4d9c467..8fbf4882 100644 --- a/src/aare/gui/panels/reference_tools_panel.py +++ b/src/aare/gui/panels/reference_tools_panel.py @@ -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): diff --git a/src/aare/gui/panels/rotation_data_collection.py b/src/aare/gui/panels/rotation_data_collection.py index fb8e455d..c01538eb 100644 --- a/src/aare/gui/panels/rotation_data_collection.py +++ b/src/aare/gui/panels/rotation_data_collection.py @@ -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.""" diff --git a/src/aare/gui/panels/samcam_panel.py b/src/aare/gui/panels/samcam_panel.py index 959b7ca6..b69e5c8b 100644 --- a/src/aare/gui/panels/samcam_panel.py +++ b/src/aare/gui/panels/samcam_panel.py @@ -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() ) diff --git a/src/aare/gui/panels/sample_queue_panel.py b/src/aare/gui/panels/sample_queue_panel.py index 5b0f609c..72dc0c8b 100644 --- a/src/aare/gui/panels/sample_queue_panel.py +++ b/src/aare/gui/panels/sample_queue_panel.py @@ -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) diff --git a/src/aare/gui/panels/scan_settings_panel.py b/src/aare/gui/panels/scan_settings_panel.py index 4352bf7c..fcddb5ae 100644 --- a/src/aare/gui/panels/scan_settings_panel.py +++ b/src/aare/gui/panels/scan_settings_panel.py @@ -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) diff --git a/src/aare/gui/panels/smargon_panel.py b/src/aare/gui/panels/smargon_panel.py index 61c3eceb..d4aa81c0 100644 --- a/src/aare/gui/panels/smargon_panel.py +++ b/src/aare/gui/panels/smargon_panel.py @@ -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)) ) diff --git a/src/aare/gui/panels/smart_rotation_panel.py b/src/aare/gui/panels/smart_rotation_panel.py index 021a0555..9f850093 100644 --- a/src/aare/gui/panels/smart_rotation_panel.py +++ b/src/aare/gui/panels/smart_rotation_panel.py @@ -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("""-""") - elif self.dtz < self.__d.bl.dtz_min: + elif self.dtz < self._d.bl.dtz_min: self.dtz_label.setText(f"""{self.dtz:.2f}""") - self.dtz = self.__d.bl.dtz_min + self.dtz = self._d.bl.dtz_min else: self.dtz_label.setText(f"{self.dtz:.2f}") diff --git a/src/aare/gui/panels/tell_sample_panel.py b/src/aare/gui/panels/tell_sample_panel.py index a0bc1075..2669bd75 100644 --- a/src/aare/gui/panels/tell_sample_panel.py +++ b/src/aare/gui/panels/tell_sample_panel.py @@ -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) diff --git a/src/aare/gui/panels/zoom_panel.py b/src/aare/gui/panels/zoom_panel.py index feff1415..27bdc8f2 100644 --- a/src/aare/gui/panels/zoom_panel.py +++ b/src/aare/gui/panels/zoom_panel.py @@ -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("") diff --git a/src/aare/gui/scan_logic/raster_grid_manager.py b/src/aare/gui/scan_logic/raster_grid_manager.py index 676346ce..64b292c1 100644 --- a/src/aare/gui/scan_logic/raster_grid_manager.py +++ b/src/aare/gui/scan_logic/raster_grid_manager.py @@ -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 diff --git a/src/aare/gui/scan_logic/rotation_scan_manager.py b/src/aare/gui/scan_logic/rotation_scan_manager.py index ec17b8e1..03885c0b 100644 --- a/src/aare/gui/scan_logic/rotation_scan_manager.py +++ b/src/aare/gui/scan_logic/rotation_scan_manager.py @@ -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) diff --git a/src/aare/gui/scan_logic/sample_mount_logic.py b/src/aare/gui/scan_logic/sample_mount_logic.py index 5b077f02..f4576681 100644 --- a/src/aare/gui/scan_logic/sample_mount_logic.py +++ b/src/aare/gui/scan_logic/sample_mount_logic.py @@ -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 diff --git a/src/aare/gui/threads/camera_thread.py b/src/aare/gui/threads/camera_thread.py index 414c60f7..310a84dc 100644 --- a/src/aare/gui/threads/camera_thread.py +++ b/src/aare/gui/threads/camera_thread.py @@ -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() diff --git a/src/aare/gui/threads/daq_worker.py b/src/aare/gui/threads/daq_worker.py index 882b18eb..f08d8213 100644 --- a/src/aare/gui/threads/daq_worker.py +++ b/src/aare/gui/threads/daq_worker.py @@ -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}") diff --git a/src/aare/gui/threads/jfjoch_viewer.py b/src/aare/gui/threads/jfjoch_viewer.py index f6b63ec6..36407b36 100644 --- a/src/aare/gui/threads/jfjoch_viewer.py +++ b/src/aare/gui/threads/jfjoch_viewer.py @@ -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) diff --git a/src/aare/gui/widgets/camera_image.py b/src/aare/gui/widgets/camera_image.py index 07d43042..7d94d217 100644 --- a/src/aare/gui/widgets/camera_image.py +++ b/src/aare/gui/widgets/camera_image.py @@ -80,40 +80,40 @@ class SampleCameraImageLabel(QGraphicsView): ): super().__init__(parent) - self.__shutter = None - self.__raster_mgr = raster - self.__state = SampleCameraImageState.IDLE - self.__session_state: SessionsStateEnum | None = None - self.__sam_cam = SampleCameraSettings(exposure=0.1, gain=100.0) - self.__is_daq_busy = False - self.__camera_available = True - self.__last_grid_update_ts = 0.0 - self.__grid_update_min_interval_s = 1.0 / 25.0 - self.__tell_state = None - self.__busy_overlay_style: BusyOverlayStyle | None = None + self._shutter = None + self._raster_mgr = raster + self._state = SampleCameraImageState.IDLE + self._session_state: SessionsStateEnum | None = None + self._sam_cam = SampleCameraSettings(exposure=0.1, gain=100.0) + self._is_daq_busy = False + self._camera_available = True + self._last_grid_update_ts = 0.0 + self._grid_update_min_interval_s = 1.0 / 25.0 + self._tell_state = None + self._busy_overlay_style: BusyOverlayStyle | None = None - self.__geom = geom - self.__bookmarks: SmargonBookmarkList = SmargonBookmarkList() - self.__autoscale = False - self.__show_coords = False - self.__helical_start = SmargonCoordinate() - self.__helical_end = SmargonCoordinate() - self.__raster_alpha = 127 - self.__bounding_box = None - self.__show_detections = True - self.__show_detection_polygons = True + self._geom = geom + self._bookmarks: SmargonBookmarkList = SmargonBookmarkList() + self._autoscale = False + self._show_coords = False + self._helical_start = SmargonCoordinate() + self._helical_end = SmargonCoordinate() + self._raster_alpha = 127 + self._bounding_box = None + self._show_detections = True + self._show_detection_polygons = True - self.__show_target_point = True - self.__show_target_coordinates = True - self.__show_overlay_legend = True - self.__compact_overlay_legend = False - self.__target_point = None - self.__target_shape = None - self.__target_color_name = "Cyan" - self.__last_target_update_ts = 0.0 - self.__target_min_update_interval_s = 0.03 - self.__target_smoothing_alpha = 0.45 - self.__smoothed_target_point: tuple[float, float] | None = None + self._show_target_point = True + self._show_target_coordinates = True + self._show_overlay_legend = True + self._compact_overlay_legend = False + self._target_point = None + self._target_shape = None + self._target_color_name = "Cyan" + self._last_target_update_ts = 0.0 + self._target_min_update_interval_s = 0.03 + self._target_smoothing_alpha = 0.45 + self._smoothed_target_point: tuple[float, float] | None = None self.start_point = None # Starting point of the rectangle self.end_point = None # Ending point of the rectangle @@ -126,7 +126,7 @@ class SampleCameraImageLabel(QGraphicsView): # events (no button pressed) only fire if the viewport tracks the mouse. # Without this the "Show coordinates" hover tooltip never appears. self.viewport().setMouseTracking(True) - self.__update_camera_interaction_feedback() + self._update_camera_interaction_feedback() if default_image is not None: pixmap = QPixmap(default_image) @@ -155,21 +155,21 @@ class SampleCameraImageLabel(QGraphicsView): self.raster_timer = QTimer() self.raster_timer.setSingleShot(True) self.raster_timer_interval = 400 - self.raster_timer.timeout.connect(self.__on_raster_timer_timeout) - self.__pending_load_pos = None + self.raster_timer.timeout.connect(self._on_raster_timer_timeout) + self._pending_load_pos = None - self.__detections = [] # list of dicts from publisher - self.__det_shape = None # shape from payload [h,w] so we can scale + self._detections = [] # list of dicts from publisher + self._det_shape = None # shape from payload [h,w] so we can scale - def __update_camera_interaction_feedback(self) -> None: - if self.__camera_available: + def _update_camera_interaction_feedback(self) -> None: + if self._camera_available: self.viewport().setCursor(Qt.CursorShape.ArrowCursor) self.setToolTip("") else: self.viewport().setCursor(Qt.CursorShape.ForbiddenCursor) self.setToolTip("Sample camera feed unavailable") - def __show_camera_unavailable_tooltip( + def _show_camera_unavailable_tooltip( self, event, action: str = "Sample camera interaction" ) -> None: QToolTip.showText( @@ -178,59 +178,59 @@ class SampleCameraImageLabel(QGraphicsView): self, ) - def __camera_interaction_enabled(self) -> bool: - return self.__camera_available + def _camera_interaction_enabled(self) -> bool: + return self._camera_available @Slot(bool) def set_camera_available(self, available: bool): - self.__camera_available = available - self.__update_camera_interaction_feedback() + self._camera_available = available + self._update_camera_interaction_feedback() self.update() @Slot(dict) def update_detections(self, payload: dict): try: - self.__det_shape = payload.get("shape", None) - self.__detections = payload.get("boxes", []) or [] + self._det_shape = payload.get("shape", None) + self._detections = payload.get("boxes", []) or [] except Exception as e: logger.debug(f"Exception in update_detections: {e}") - self.__detections = [] + self._detections = [] self.update() @Slot(dict) def update_target_point(self, payload: dict): now = time.monotonic() - if now - self.__last_target_update_ts < self.__target_min_update_interval_s: + if now - self._last_target_update_ts < self._target_min_update_interval_s: return try: - self.__target_shape = payload.get("shape", self.__det_shape) + self._target_shape = payload.get("shape", self._det_shape) raw_target = payload.get("target_point") - target_xy = self.__coerce_target_point(raw_target) + target_xy = self._coerce_target_point(raw_target) if target_xy is None: return - self.__target_point = raw_target - if self.__smoothed_target_point is None: - self.__smoothed_target_point = target_xy + self._target_point = raw_target + if self._smoothed_target_point is None: + self._smoothed_target_point = target_xy else: - px, py = self.__smoothed_target_point + px, py = self._smoothed_target_point nx, ny = target_xy - alpha = self.__target_smoothing_alpha - self.__smoothed_target_point = ( + alpha = self._target_smoothing_alpha + self._smoothed_target_point = ( (1.0 - alpha) * px + alpha * nx, (1.0 - alpha) * py + alpha * ny, ) - self.__last_target_update_ts = now + self._last_target_update_ts = now except Exception as e: logger.debug(f"Exception in update_target_point: {e}") - self.__target_point = None - self.__smoothed_target_point = None + self._target_point = None + self._smoothed_target_point = None self.update() - def __busy_overlay_text(self) -> str: - tell_state = self.__tell_state + def _busy_overlay_text(self) -> str: + tell_state = self._tell_state if tell_state is None: return "BEAMLINE BUSY" @@ -242,11 +242,11 @@ class SampleCameraImageLabel(QGraphicsView): return f"TELL {activity_name}".upper() - def __draw_busy_overlay(self, painter: QPainter): - if self.__busy_overlay_style is None: + def _draw_busy_overlay(self, painter: QPainter): + if self._busy_overlay_style is None: return - style = self.__busy_overlay_style + style = self._busy_overlay_style painter.save() painter.resetTransform() @@ -282,11 +282,11 @@ class SampleCameraImageLabel(QGraphicsView): painter.restore() - def __draw_session_overlay(self, painter: QPainter): - if self.__busy_overlay_style is not None: + def _draw_session_overlay(self, painter: QPainter): + if self._busy_overlay_style is not None: return - if self.__session_state in ( + if self._session_state in ( SessionsStateEnum.OwnedByYou, SessionsStateEnum.PendingElseToYou, ): @@ -300,10 +300,10 @@ class SampleCameraImageLabel(QGraphicsView): font.setBold(True) painter.setFont(font) - if self.__session_state == SessionsStateEnum.Vacant: + if self._session_state == SessionsStateEnum.Vacant: bg_color = QColor(255, 215, 0, 180) text = "Session Vacant" - elif self.__session_state == SessionsStateEnum.PendingYouToElse: + elif self._session_state == SessionsStateEnum.PendingYouToElse: bg_color = QColor(255, 165, 0, 150) text = "Baton Requested..." else: @@ -333,8 +333,8 @@ class SampleCameraImageLabel(QGraphicsView): painter.restore() - def __draw_camera_unavailable_overlay(self, painter: QPainter): - if self.__camera_available: + def _draw_camera_unavailable_overlay(self, painter: QPainter): + if self._camera_available: return painter.save() @@ -370,89 +370,89 @@ class SampleCameraImageLabel(QGraphicsView): painter.restore() def drawForeground(self, painter, rect): - self.__draw_ml_bounding_box(painter) - self.__draw_beam_center(painter) - self.__raster_mgr.draw_grid(painter, self.__raster_alpha) - self.__draw_helical(painter) - self.__draw_busy_overlay(painter) - self.__draw_session_overlay(painter) - self.__draw_camera_unavailable_overlay(painter) - self.__draw_detections(painter, rect) - self.__draw_target_point(painter) - self.__draw_overlay_legend(painter) + self._draw_ml_bounding_box(painter) + self._draw_beam_center(painter) + self._raster_mgr.draw_grid(painter, self._raster_alpha) + self._draw_helical(painter) + self._draw_busy_overlay(painter) + self._draw_session_overlay(painter) + self._draw_camera_unavailable_overlay(painter) + self._draw_detections(painter, rect) + self._draw_target_point(painter) + self._draw_overlay_legend(painter) def resizeEvent(self, event): super().resizeEvent(event) - self.__scaling() + self._scaling() def mousePressEvent(self, event): - if not self.__camera_interaction_enabled(): + if not self._camera_interaction_enabled(): if event.button() in (Qt.MouseButton.LeftButton, Qt.MouseButton.RightButton): - self.__show_camera_unavailable_tooltip(event) + self._show_camera_unavailable_tooltip(event) event.accept() return self.start_point = self.mapToScene(event.pos()) ctrl_override_move = bool(event.modifiers() & Qt.KeyboardModifier.ControlModifier) - match self.__state: + match self._state: case SampleCameraImageState.BEAM_MARKING: self.click_timer.start(self.right_click_hold_threshold) case SampleCameraImageState.IDLE: self.click_timer.start(self.right_click_hold_threshold) if event.button() == Qt.MouseButton.RightButton: - if self.__raster_mgr.is_part_of_active_grid(self.start_point): - self.__state = SampleCameraImageState.RESIZE_RASTER_GRID + if self._raster_mgr.is_part_of_active_grid(self.start_point): + self._state = SampleCameraImageState.RESIZE_RASTER_GRID else: - self.__state = SampleCameraImageState.DRAWING_RASTER_GRID + self._state = SampleCameraImageState.DRAWING_RASTER_GRID elif event.button() == Qt.MouseButton.LeftButton: if ( - self.__raster_mgr.is_part_of_active_grid(self.start_point) + self._raster_mgr.is_part_of_active_grid(self.start_point) and not ctrl_override_move ): - self.__state = SampleCameraImageState.MOVING_RASTER_GRID + self._state = SampleCameraImageState.MOVING_RASTER_GRID def _update_grid(self): now = time.monotonic() - if (now - self.__last_grid_update_ts) < self.__grid_update_min_interval_s: + if (now - self._last_grid_update_ts) < self._grid_update_min_interval_s: return - self.__last_grid_update_ts = now + self._last_grid_update_ts = now - match self.__state: + match self._state: case SampleCameraImageState.DRAWING_RASTER_GRID: self.switch_raster_grid.emit() - self.__raster_mgr.update_active_grid(self.start_point, self.end_point) + self._raster_mgr.update_active_grid(self.start_point, self.end_point) case SampleCameraImageState.MOVING_RASTER_GRID: self.switch_raster_grid.emit() - self.__raster_mgr.move_active_grid(self.end_point - self.start_point) + self._raster_mgr.move_active_grid(self.end_point - self.start_point) self.start_point = self.end_point case SampleCameraImageState.RESIZE_RASTER_GRID: self.switch_raster_grid.emit() - self.__raster_mgr.resize_active_grid(self.end_point) + self._raster_mgr.resize_active_grid(self.end_point) def mouseMoveEvent(self, event): - if not self.__camera_interaction_enabled(): + if not self._camera_interaction_enabled(): return mouse_pos = self.mapToScene(event.pos()) # Hover tooltips (completed-grid info / pixel coordinates) should work in # any state, not only IDLE -- e.g. while beam-marking in beam location. - grid_txt = self.__raster_mgr.is_part_of_completed_grid(mouse_pos) + grid_txt = self._raster_mgr.is_part_of_completed_grid(mouse_pos) if grid_txt: QToolTip.showText(self.mapToGlobal(event.pos()), grid_txt, self) - elif self.__show_coords: + elif self._show_coords: QToolTip.showText( self.mapToGlobal(event.pos()), f"{mouse_pos.x():.0f}, {mouse_pos.y():.0f} pxl", self ) - if self.__state == SampleCameraImageState.IDLE: + if self._state == SampleCameraImageState.IDLE: if event.modifiers() & Qt.KeyboardModifier.ShiftModifier: if not self.raster_timer.isActive(): self.load_image.emit(mouse_pos) self.raster_timer.start(self.raster_timer_interval) else: - self.__pending_load_pos = mouse_pos + self._pending_load_pos = mouse_pos self.end_point = mouse_pos @@ -461,60 +461,60 @@ class SampleCameraImageLabel(QGraphicsView): self._update_grid() self.update() - def __on_raster_timer_timeout(self): - if self.__pending_load_pos is not None: - self.load_image.emit(self.__pending_load_pos) - self.__pending_load_pos = None + def _on_raster_timer_timeout(self): + if self._pending_load_pos is not None: + self.load_image.emit(self._pending_load_pos) + self._pending_load_pos = None return - if self.__pending_load_pos is not None: - self.load_image.emit(self.__pending_load_pos) - self.__pending_load_pos = None + if self._pending_load_pos is not None: + self.load_image.emit(self._pending_load_pos) + self._pending_load_pos = None self.raster_timer.start(self.raster_timer_interval) def mouseReleaseEvent(self, event): - if not self.__camera_interaction_enabled(): + if not self._camera_interaction_enabled(): self.click_timer.stop() - if self.__state != SampleCameraImageState.BEAM_MARKING: - self.__state = SampleCameraImageState.IDLE + if self._state != SampleCameraImageState.BEAM_MARKING: + self._state = SampleCameraImageState.IDLE event.accept() return if event.button() == Qt.MouseButton.LeftButton: if self.click_timer.isActive(): self.click_timer.stop() - self.__left_single_click(event) + self._left_single_click(event) else: self._update_grid() self.update() if event.button() == Qt.MouseButton.RightButton: if self.click_timer.isActive(): self.click_timer.stop() - self.__right_click_menu(event) + self._right_click_menu(event) else: self._update_grid() self.update() - if self.__state != SampleCameraImageState.BEAM_MARKING: - self.__state = SampleCameraImageState.IDLE + if self._state != SampleCameraImageState.BEAM_MARKING: + self._state = SampleCameraImageState.IDLE - def __right_click_menu(self, event): - if not self.__camera_interaction_enabled(): - self.__show_camera_unavailable_tooltip(event) + def _right_click_menu(self, event): + if not self._camera_interaction_enabled(): + self._show_camera_unavailable_tooltip(event) return menu = QMenu(self) scale_action = menu.addAction("Scale to fit") scale_action.setCheckable(True) - scale_action.setChecked(self.__autoscale) + scale_action.setChecked(self._autoscale) show_coord_action = menu.addAction("Show coordinates") show_coord_action.setCheckable(True) - show_coord_action.setChecked(self.__show_coords) + show_coord_action.setChecked(self._show_coords) show_detections_action = menu.addAction("Show ML predictions") show_detections_action.setCheckable(True) - show_detections_action.setChecked(self.__show_detections) + show_detections_action.setChecked(self._show_detections) grab_action = menu.addAction("Grab") grab_with_overlay_action = menu.addAction("Grab with overlay") @@ -530,7 +530,7 @@ class SampleCameraImageLabel(QGraphicsView): menu.addSection("Grid") - if self.__raster_mgr.is_part_of_active_grid(scene_pos): + if self._raster_mgr.is_part_of_active_grid(scene_pos): delete_action = menu.addAction("Delete grid") evaluate_action = menu.addAction("Evaluate grid") delete_completed_action = menu.addAction("Delete completed grids") @@ -540,16 +540,16 @@ class SampleCameraImageLabel(QGraphicsView): if action is None: return elif action == show_coord_action: - self.__show_coords = not self.__show_coords + self._show_coords = not self._show_coords logger.info( - f"Show-coordinates hover tooltip {'enabled' if self.__show_coords else 'disabled'}" + f"Show-coordinates hover tooltip {'enabled' if self._show_coords else 'disabled'}" ) self.update() elif action == scale_action: - self.__autoscale = not self.__autoscale - self.__scaling() + self._autoscale = not self._autoscale + self._scaling() elif action == show_detections_action: - self.__show_detections = not self.__show_detections + self._show_detections = not self._show_detections self.update() elif action == delete_action: self.clear_grid.emit() @@ -558,9 +558,9 @@ class SampleCameraImageLabel(QGraphicsView): elif action == delete_completed_action: self.clear_evaluated_grids.emit() elif action == grab_action: - self.__screenshot_with_dialog(overlay=False) + self._screenshot_with_dialog(overlay=False) elif action == grab_with_overlay_action: - self.__screenshot_with_dialog(overlay=True) + self._screenshot_with_dialog(overlay=True) elif action == autofocus_action: self.autofocus.emit( AutofocusSettings( @@ -572,15 +572,15 @@ class SampleCameraImageLabel(QGraphicsView): self.update_beam_mark.emit(c.x(), c.y()) self.update() - def __screenshot_with_dialog(self, overlay: bool): + def _screenshot_with_dialog(self, overlay: bool): file_path, _ = QFileDialog.getSaveFileName( self, "Save View As", "", "JPEG Files (*.jpg; *.jpeg);;All Files (*)" ) if file_path: - self.__screenshot(file_path=file_path, overlay=overlay) + self._screenshot(file_path=file_path, overlay=overlay) - def __screenshot(self, file_path: str, overlay: bool): + def _screenshot(self, file_path: str, overlay: bool): scene_rect = self.scene.sceneRect() pixmap = QPixmap(scene_rect.size().toSize()) painter = QPainter(pixmap) @@ -590,8 +590,8 @@ class SampleCameraImageLabel(QGraphicsView): painter.end() pixmap.save(file_path, "JPEG") - def __scaling(self): - if not self.__autoscale: + def _scaling(self): + if not self._autoscale: self.setTransform(QTransform()) self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) @@ -636,42 +636,42 @@ class SampleCameraImageLabel(QGraphicsView): @Slot(DAQStatusModel) def update_daq_status(self, s: DAQStatusModel): - self.__geom = s.geom - self.__sam_cam = s.bl.sample_camera - self.__bounding_box = s.box - self.__tell_state = s.tell_state + self._geom = s.geom + self._sam_cam = s.bl.sample_camera + self._bounding_box = s.box + self._tell_state = s.tell_state new_session_state = s.session.session if hasattr(s, "session") else None - if new_session_state != self.__session_state: - self.__session_state = new_session_state + if new_session_state != self._session_state: + self._session_state = new_session_state self.update() new_busy_style = build_busy_overlay_style( - is_busy=bool(s.busy), tell_state=s.tell_state, session_state=self.__session_state + is_busy=bool(s.busy), tell_state=s.tell_state, session_state=self._session_state ) - if new_busy_style != self.__busy_overlay_style: - self.__busy_overlay_style = new_busy_style + if new_busy_style != self._busy_overlay_style: + self._busy_overlay_style = new_busy_style self.update() - if s.bl.shutter_open != self.__shutter: - self.__shutter = s.bl.shutter_open + if s.bl.shutter_open != self._shutter: + self._shutter = s.bl.shutter_open - if s.busy != self.__is_daq_busy: - self.__is_daq_busy = s.busy + if s.busy != self._is_daq_busy: + self._is_daq_busy = s.busy self.update() if s.state == BeamlineStateEnum.BeamLocation: - self.__state = SampleCameraImageState.BEAM_MARKING - elif self.__state == SampleCameraImageState.BEAM_MARKING: - self.__state = SampleCameraImageState.IDLE + self._state = SampleCameraImageState.BEAM_MARKING + elif self._state == SampleCameraImageState.BEAM_MARKING: + self._state = SampleCameraImageState.IDLE - def __left_single_click(self, event): - if not self.__camera_interaction_enabled(): + def _left_single_click(self, event): + if not self._camera_interaction_enabled(): logger.info("Ignoring click because sample camera feed is unavailable") - self.__show_camera_unavailable_tooltip(event, "Point-on-click") + self._show_camera_unavailable_tooltip(event, "Point-on-click") return - match self.__state: + match self._state: case SampleCameraImageState.BEAM_MARKING: if ( event.button() == Qt.MouseButton.LeftButton @@ -686,21 +686,21 @@ class SampleCameraImageLabel(QGraphicsView): return if event.modifiers() & Qt.KeyboardModifier.ShiftModifier: - sc = SmargonCoordinate(sh_mm=self.__geom.smargon_z(point.y())) + sc = SmargonCoordinate(sh_mm=self._geom.smargon_z(point.y())) self.smargon.emit(sc) else: - sample_coord = self.__geom.picture_to_sample( + sample_coord = self._geom.picture_to_sample( Coordinate(x=point.x(), y=point.y()) ) smargon_coord = SmargonCoordinate( - sh_mm=self.__geom.beamline_to_smargon(sample_coord) + sh_mm=self._geom.beamline_to_smargon(sample_coord) ) self.smargon.emit(smargon_coord) - def __draw_detections(self, painter: QPainter, rect): - if not self.__show_detections: + def _draw_detections(self, painter: QPainter, rect): + if not self._show_detections: return - if not getattr(self, "_SampleCameraImageLabel__detections", None): + if not getattr(self, "_detections", None): return if self.pixmap_item is None: return @@ -708,7 +708,7 @@ class SampleCameraImageLabel(QGraphicsView): if pix.isNull(): return - det_shape = self.__det_shape + det_shape = self._det_shape if det_shape is None: return img_h, img_w = det_shape[0], det_shape[1] @@ -726,7 +726,7 @@ class SampleCameraImageLabel(QGraphicsView): "ice": QColor("cyan"), } - for det in self.__detections: + for det in self._detections: try: x1 = det["x1"] * sx y1 = det["y1"] * sy @@ -748,7 +748,7 @@ class SampleCameraImageLabel(QGraphicsView): painter.drawRect(detection_rect) # Only draw polygon if the separate toggle is enabled - if self.__show_detection_polygons and poly and len(poly) >= 3: + if self._show_detection_polygons and poly and len(poly) >= 3: polygon = QPolygonF( [QPointF(x1 + float(p[0]) * sx, y1 + float(p[1]) * sy) for p in poly] ) @@ -761,15 +761,15 @@ class SampleCameraImageLabel(QGraphicsView): painter.setPen(QPen(QColor(255, 255, 255))) painter.drawText(QPoint(int(x1) + 2, int(y1 - 4)), f"{label} {conf:.2f}") - def __target_color(self) -> QColor: + def _target_color(self) -> QColor: color_map = { "Cyan": QColor(0, 255, 255), "Dark Blue": QColor(0, 70, 160), "Dark Red": QColor(140, 25, 25), } - return color_map.get(self.__target_color_name, QColor(0, 255, 255)) + return color_map.get(self._target_color_name, QColor(0, 255, 255)) - def __coerce_target_point(self, raw) -> tuple[float, float] | None: + def _coerce_target_point(self, raw) -> tuple[float, float] | None: try: if isinstance(raw, dict): return float(raw["x"]), float(raw["y"]) @@ -779,10 +779,10 @@ class SampleCameraImageLabel(QGraphicsView): logger.debug(f"Error parsing target point {raw}: {e}") return None - def __draw_target_point(self, painter: QPainter): - if not self.__show_target_point: + def _draw_target_point(self, painter: QPainter): + if not self._show_target_point: return - if self.__smoothed_target_point is None: + if self._smoothed_target_point is None: return if self.pixmap_item is None: return @@ -791,15 +791,15 @@ class SampleCameraImageLabel(QGraphicsView): if pix.isNull(): return - shape = self.__target_shape or self.__det_shape + shape = self._target_shape or self._det_shape if shape is None or len(shape) < 2: return try: img_h, img_w = int(shape[0]), int(shape[1]) - tx, ty = self.__smoothed_target_point + tx, ty = self._smoothed_target_point except Exception as e: - logger.debug(f"Error using smoothed target point {self.__smoothed_target_point}: {e}") + logger.debug(f"Error using smoothed target point {self._smoothed_target_point}: {e}") return sx = pix.width() / float(img_w) @@ -808,7 +808,7 @@ class SampleCameraImageLabel(QGraphicsView): px = tx * sx py = ty * sy - color = self.__target_color() + color = self._target_color() glow = QColor(color) glow.setAlpha(70) @@ -832,7 +832,7 @@ class SampleCameraImageLabel(QGraphicsView): painter.drawLine(QPointF(px, py + 7), QPointF(px, py + 20)) label_text = "Target" - if self.__show_target_coordinates: + if self._show_target_coordinates: label_text = f"Target ({tx:.1f}, {ty:.1f})" font = QFont() @@ -855,25 +855,25 @@ class SampleCameraImageLabel(QGraphicsView): painter.restore() - def __legend_should_show(self) -> bool: - return self.__show_overlay_legend and ( - self.__show_target_point - or self.__show_detections - or self.__show_coords - or self.__state == SampleCameraImageState.BEAM_MARKING + def _legend_should_show(self) -> bool: + return self._show_overlay_legend and ( + self._show_target_point + or self._show_detections + or self._show_coords + or self._state == SampleCameraImageState.BEAM_MARKING ) - def __legend_lines(self) -> list[tuple[str, QColor | None]]: + def _legend_lines(self) -> list[tuple[str, QColor | None]]: lines: list[tuple[str, QColor | None]] = [] - if self.__compact_overlay_legend: - if self.__show_target_point: + if self._compact_overlay_legend: + if self._show_target_point: target_label = "Target" - if self.__show_target_coordinates: + if self._show_target_coordinates: target_label += " + coords" - lines.append((target_label, self.__target_color())) + lines.append((target_label, self._target_color())) - if self.__show_detections: + if self._show_detections: lines.extend( [ ("Pin", QColor("red")), @@ -883,7 +883,7 @@ class SampleCameraImageLabel(QGraphicsView): ] ) - if self.__show_coords: + if self._show_coords: lines.append(("Coords tooltip", QColor(230, 230, 230))) lines.append(("Beam marker: shutter open", QColor(0, 255, 0))) @@ -891,13 +891,13 @@ class SampleCameraImageLabel(QGraphicsView): lines.append(("Beam marker: busy", QColor(255, 0, 0))) return lines - if self.__show_target_point: - target_label = f"Target marker ({self.__target_color_name})" - if self.__show_target_coordinates: + if self._show_target_point: + target_label = f"Target marker ({self._target_color_name})" + if self._show_target_coordinates: target_label += " + coords" - lines.append((target_label, self.__target_color())) + lines.append((target_label, self._target_color())) - if self.__show_detections: + if self._show_detections: lines.extend( [ ("Prediction: Pin", QColor("red")), @@ -909,7 +909,7 @@ class SampleCameraImageLabel(QGraphicsView): ] ) - if self.__show_coords: + if self._show_coords: lines.append(("Cursor tooltip: pixel coordinates", QColor(230, 230, 230))) lines.append(("Beam marker: shutter open", QColor(0, 255, 0))) @@ -919,8 +919,8 @@ class SampleCameraImageLabel(QGraphicsView): return lines - def __draw_overlay_legend(self, painter: QPainter): - if not self.__legend_should_show(): + def _draw_overlay_legend(self, painter: QPainter): + if not self._legend_should_show(): return painter.save() @@ -928,15 +928,15 @@ class SampleCameraImageLabel(QGraphicsView): painter.setRenderHint(QPainter.RenderHint.Antialiasing, True) font = QFont() - font.setPointSize(8 if self.__compact_overlay_legend else 9) + font.setPointSize(8 if self._compact_overlay_legend else 9) painter.setFont(font) fm = QFontMetrics(font) - lines = self.__legend_lines() - line_height = max(14 if self.__compact_overlay_legend else 16, fm.height() + 2) - swatch_size = 8 if self.__compact_overlay_legend else 10 - text_padding = 6 if self.__compact_overlay_legend else 8 - section_padding = 8 if self.__compact_overlay_legend else 10 + lines = self._legend_lines() + line_height = max(14 if self._compact_overlay_legend else 16, fm.height() + 2) + swatch_size = 8 if self._compact_overlay_legend else 10 + text_padding = 6 if self._compact_overlay_legend else 8 + section_padding = 8 if self._compact_overlay_legend else 10 left = 18 top = self.viewport().height() - (len(lines) * line_height + 24) @@ -978,52 +978,52 @@ class SampleCameraImageLabel(QGraphicsView): @Slot(bool) def set_show_detections(self, show: bool): - self.__show_detections = show + self._show_detections = show self.update() @Slot(bool) def set_show_detection_polygons(self, show: bool): - self.__show_detection_polygons = show + self._show_detection_polygons = show self.update() @Slot(bool) def set_show_target_point(self, show: bool): - self.__show_target_point = show + self._show_target_point = show self.update() @Slot(bool) def set_show_target_coordinates(self, show: bool): - self.__show_target_coordinates = show + self._show_target_coordinates = show self.update() @Slot(bool) def set_show_overlay_legend(self, show: bool): - self.__show_overlay_legend = show + self._show_overlay_legend = show self.update() @Slot(bool) def set_compact_overlay_legend(self, compact: bool): - self.__compact_overlay_legend = compact + self._compact_overlay_legend = compact self.update() @Slot(str) def set_target_color(self, color_name: str): - self.__target_color_name = color_name + self._target_color_name = color_name self.update() def target_overlay_settings(self) -> dict: return { - "show_target_point": self.__show_target_point, - "show_target_coordinates": self.__show_target_coordinates, - "show_detections": self.__show_detections, - "show_detection_polygons": self.__show_detection_polygons, - "show_overlay_legend": self.__show_overlay_legend, - "compact_overlay_legend": self.__compact_overlay_legend, - "target_color": self.__target_color_name, + "show_target_point": self._show_target_point, + "show_target_coordinates": self._show_target_coordinates, + "show_detections": self._show_detections, + "show_detection_polygons": self._show_detection_polygons, + "show_overlay_legend": self._show_overlay_legend, + "compact_overlay_legend": self._compact_overlay_legend, + "target_color": self._target_color_name, } - def __draw_ml_bounding_box(self, painter: QPainter): - if self.__bounding_box is None: + def _draw_ml_bounding_box(self, painter: QPainter): + if self._bounding_box is None: return painter.setPen(QPen(QColor(50, 205, 50), 3, Qt.PenStyle.SolidLine)) @@ -1031,23 +1031,23 @@ class SampleCameraImageLabel(QGraphicsView): painter.setBrush(Qt.BrushStyle.NoBrush) painter.drawRect( QRect( - int(self.__bounding_box.bottom_x), - int(self.__bounding_box.bottom_y), - int(self.__bounding_box.top_x - self.__bounding_box.bottom_x), - int(self.__bounding_box.top_y - self.__bounding_box.bottom_y), + int(self._bounding_box.bottom_x), + int(self._bounding_box.bottom_y), + int(self._bounding_box.top_x - self._bounding_box.bottom_x), + int(self._bounding_box.top_y - self._bounding_box.bottom_y), ) ) - def __draw_beam_center(self, painter: QPainter): - beam_size_pxl = self.__geom.beam_size_pxl + def _draw_beam_center(self, painter: QPainter): + beam_size_pxl = self._geom.beam_size_pxl - if self.__state == SampleCameraImageState.BEAM_MARKING: + if self._state == SampleCameraImageState.BEAM_MARKING: painter.setPen(QPen(QColor(102, 51, 153), 3, Qt.PenStyle.SolidLine)) - elif self.__shutter: + elif self._shutter: painter.setPen(QPen(QColor(0, 255, 0), 3, Qt.PenStyle.SolidLine)) - elif self.__is_daq_busy is True: + elif self._is_daq_busy is True: # Use red color to indicate busy state painter.setPen(QPen(QColor(255, 0, 0), 3, Qt.PenStyle.SolidLine)) else: @@ -1056,21 +1056,21 @@ class SampleCameraImageLabel(QGraphicsView): painter.setBrush(Qt.BrushStyle.NoBrush) painter.drawRect( QRect( - int(self.__geom.beam_location_pxl.x - beam_size_pxl.x / 2), - int(self.__geom.beam_location_pxl.y - beam_size_pxl.y / 2), + int(self._geom.beam_location_pxl.x - beam_size_pxl.x / 2), + int(self._geom.beam_location_pxl.y - beam_size_pxl.y / 2), int(beam_size_pxl.x), int(beam_size_pxl.y), ) ) @staticmethod - def __draw_circle(painter, coord: Coordinate, color: QColor, radius=10): + def _draw_circle(painter, coord: Coordinate, color: QColor, radius=10): painter.setPen(QPen(Qt.PenStyle.NoPen)) painter.setBrush(color) painter.drawEllipse(coord.x - radius, coord.y - radius, 2 * radius, 2 * radius) @staticmethod - def __draw_arrow(painter: QPainter, start: Coordinate, end: Coordinate): + def _draw_arrow(painter: QPainter, start: Coordinate, end: Coordinate): gradient = QLinearGradient() gradient.setStart(QPointF(start.x, start.y)) # Start of the gradient (green) gradient.setFinalStop(QPointF(end.x, end.y)) # End of the gradient (red) @@ -1086,36 +1086,36 @@ class SampleCameraImageLabel(QGraphicsView): painter.drawLine(QPointF(start.x, start.y), QPointF(end.x, end.y)) - def __draw_helical(self, painter: QPainter): - if self.__helical_start.sh_mm is not None: - start_pxl = self.__geom.smargon_to_picture(self.__helical_start.sh_mm) - self.__draw_circle(painter, start_pxl, QColor("green")) + def _draw_helical(self, painter: QPainter): + if self._helical_start.sh_mm is not None: + start_pxl = self._geom.smargon_to_picture(self._helical_start.sh_mm) + self._draw_circle(painter, start_pxl, QColor("green")) else: start_pxl = None - if self.__helical_end.sh_mm is not None: - end_pxl = self.__geom.smargon_to_picture(self.__helical_end.sh_mm) - self.__draw_circle(painter, end_pxl, QColor("red")) + if self._helical_end.sh_mm is not None: + end_pxl = self._geom.smargon_to_picture(self._helical_end.sh_mm) + self._draw_circle(painter, end_pxl, QColor("red")) else: end_pxl = None if start_pxl is not None and end_pxl is not None: - self.__draw_arrow(painter, start_pxl, end_pxl) + self._draw_arrow(painter, start_pxl, end_pxl) @Slot(SmargonCoordinate) def show_helical_start(self, pos: SmargonCoordinate): - self.__helical_start = pos + self._helical_start = pos self.update() @Slot(SmargonCoordinate) def show_helical_end(self, pos: SmargonCoordinate): - self.__helical_end = pos + self._helical_end = pos self.update() @Slot(int) def raster_alpha(self, value: int): if 0 <= value <= 255: - self.__raster_alpha = value + self._raster_alpha = value def keyPressEvent(self, event): mouse_global_pos = QCursor.pos() @@ -1123,7 +1123,7 @@ class SampleCameraImageLabel(QGraphicsView): mouse_scene_pos = self.mapToScene(mouse_view_pos) if event.key() == Qt.Key.Key_Shift: - if self.__state == SampleCameraImageState.IDLE and self.__camera_interaction_enabled(): + if self._state == SampleCameraImageState.IDLE and self._camera_interaction_enabled(): if not self.raster_timer.isActive(): self.load_image.emit(mouse_scene_pos) self.raster_timer.start(self.raster_timer_interval) @@ -1133,37 +1133,37 @@ class SampleCameraImageLabel(QGraphicsView): def wheelEvent(self, event: QWheelEvent): if not self.wheel_event_timer.isActive(): delta_y = event.angleDelta().y() - match self.__state: + match self._state: case SampleCameraImageState.BEAM_MARKING: if event.modifiers() & Qt.KeyboardModifier.AltModifier: - new_exp_time = self.__sam_cam.exposure * (1.0 + math.copysign(0.1, delta_y)) + new_exp_time = self._sam_cam.exposure * (1.0 + math.copysign(0.1, delta_y)) new_settings = SampleCameraSettings( - exposure=new_exp_time, gain=self.__sam_cam.gain + exposure=new_exp_time, gain=self._sam_cam.gain ) self.samcam_updated.emit(new_settings) else: - new_exp_time = self.__sam_cam.exposure * (1.0 + math.copysign(0.5, delta_y)) + new_exp_time = self._sam_cam.exposure * (1.0 + math.copysign(0.5, delta_y)) new_settings = SampleCameraSettings( - exposure=new_exp_time, gain=self.__sam_cam.gain + exposure=new_exp_time, gain=self._sam_cam.gain ) self.samcam_updated.emit(new_settings) case SampleCameraImageState.IDLE: if event.modifiers() & Qt.KeyboardModifier.AltModifier: - new_exp_time = self.__sam_cam.exposure * (1.0 + math.copysign(0.1, delta_y)) + new_exp_time = self._sam_cam.exposure * (1.0 + math.copysign(0.1, delta_y)) new_settings = SampleCameraSettings( - exposure=new_exp_time, gain=self.__sam_cam.gain + exposure=new_exp_time, gain=self._sam_cam.gain ) self.samcam_updated.emit(new_settings) elif event.modifiers() & Qt.KeyboardModifier.ControlModifier: - new_exp_time = self.__sam_cam.exposure * (1.0 + math.copysign(0.5, delta_y)) + new_exp_time = self._sam_cam.exposure * (1.0 + math.copysign(0.5, delta_y)) new_settings = SampleCameraSettings( - exposure=new_exp_time, gain=self.__sam_cam.gain + exposure=new_exp_time, gain=self._sam_cam.gain ) self.samcam_updated.emit(new_settings) elif event.modifiers() & Qt.KeyboardModifier.ShiftModifier: - self.set_omega.emit(self.__geom.omega_deg + math.copysign(10.0, delta_y)) + self.set_omega.emit(self._geom.omega_deg + math.copysign(10.0, delta_y)) else: - self.set_omega.emit(self.__geom.omega_deg + math.copysign(90.0, delta_y)) + self.set_omega.emit(self._geom.omega_deg + math.copysign(90.0, delta_y)) # Start the timer to throttle further events self.wheel_event_timer.start(self.wheel_event_threshold) diff --git a/src/aare/gui/widgets/login.py b/src/aare/gui/widgets/login.py index 4c947b25..38d8a1df 100644 --- a/src/aare/gui/widgets/login.py +++ b/src/aare/gui/widgets/login.py @@ -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() diff --git a/src/aare/gui/widgets/raster_grid_table.py b/src/aare/gui/widgets/raster_grid_table.py index 714f7205..0c40345f 100644 --- a/src/aare/gui/widgets/raster_grid_table.py +++ b/src/aare/gui/widgets/raster_grid_table.py @@ -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() diff --git a/src/aare/gui/widgets/status_bar.py b/src/aare/gui/widgets/status_bar.py index ad8f4af3..34506549 100644 --- a/src/aare/gui/widgets/status_bar.py +++ b/src/aare/gui/widgets/status_bar.py @@ -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 diff --git a/src/aare/gui/widgets/value_label.py b/src/aare/gui/widgets/value_label.py index c56c50dd..05ffb7ed 100644 --- a/src/aare/gui/widgets/value_label.py +++ b/src/aare/gui/widgets/value_label.py @@ -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}: {s} {self.__unit} """ + f"""{self._descr}: {s} {self._unit} """ ) else: - self.setText(f"{self.__descr}: {s} {self.__unit}") + self.setText(f"{self._descr}: {s} {self._unit}") def mousePressEvent(self, event): if event.button() == Qt.MouseButton.LeftButton: diff --git a/tests/unit/daq/test_aare_daq_loop_centering.py b/tests/unit/daq/test_aare_daq_loop_centering.py index 39b1a209..eb3f9ea2 100644 --- a/tests/unit/daq/test_aare_daq_loop_centering.py +++ b/tests/unit/daq/test_aare_daq_loop_centering.py @@ -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() diff --git a/tests/unit/daq/test_aaredb.py b/tests/unit/daq/test_aaredb.py index 851b8213..311a352f 100644 --- a/tests/unit/daq/test_aaredb.py +++ b/tests/unit/daq/test_aaredb.py @@ -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") diff --git a/tests/unit/daq/test_auth.py b/tests/unit/daq/test_auth.py index 85d2c856..c99ad461 100644 --- a/tests/unit/daq/test_auth.py +++ b/tests/unit/daq/test_auth.py @@ -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): diff --git a/tests/unit/daq/test_automation_progress_state_manager.py b/tests/unit/daq/test_automation_progress_state_manager.py index e936ea75..8d6f2b3c 100644 --- a/tests/unit/daq/test_automation_progress_state_manager.py +++ b/tests/unit/daq/test_automation_progress_state_manager.py @@ -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 diff --git a/tests/unit/daq/test_face_detection.py b/tests/unit/daq/test_face_detection.py index 5518d4a0..e1075e09 100644 --- a/tests/unit/daq/test_face_detection.py +++ b/tests/unit/daq/test_face_detection.py @@ -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, diff --git a/tests/unit/daq/test_mlbox.py b/tests/unit/daq/test_mlbox.py index c789def4..98ef9a9e 100644 --- a/tests/unit/daq/test_mlbox.py +++ b/tests/unit/daq/test_mlbox.py @@ -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(): diff --git a/tests/unit/daq/test_mount.py b/tests/unit/daq/test_mount.py index caf6a2c6..808272fd 100644 --- a/tests/unit/daq/test_mount.py +++ b/tests/unit/daq/test_mount.py @@ -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) diff --git a/tests/unit/daq/test_spreadsheetupdater.py b/tests/unit/daq/test_spreadsheetupdater.py index c14213bd..2255bb87 100644 --- a/tests/unit/daq/test_spreadsheetupdater.py +++ b/tests/unit/daq/test_spreadsheetupdater.py @@ -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() diff --git a/tests/unit/daq/test_tellupdater.py b/tests/unit/daq/test_tellupdater.py index 7147558a..2fbfc91a 100644 --- a/tests/unit/daq/test_tellupdater.py +++ b/tests/unit/daq/test_tellupdater.py @@ -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) diff --git a/tests/unit/devices/test_aerotech.py b/tests/unit/devices/test_aerotech.py index a8fdc3b4..add7b7f9 100644 --- a/tests/unit/devices/test_aerotech.py +++ b/tests/unit/devices/test_aerotech.py @@ -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): diff --git a/tests/unit/devices/test_experimental_hutch_shutter.py b/tests/unit/devices/test_experimental_hutch_shutter.py index 024e9272..fdfed9c8 100644 --- a/tests/unit/devices/test_experimental_hutch_shutter.py +++ b/tests/unit/devices/test_experimental_hutch_shutter.py @@ -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] diff --git a/tests/unit/devices/test_jfjoch.py b/tests/unit/devices/test_jfjoch.py index 8250bcc4..8c5a7d13 100644 --- a/tests/unit/devices/test_jfjoch.py +++ b/tests/unit/devices/test_jfjoch.py @@ -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(): diff --git a/tests/unit/gui/test_camera_thread.py b/tests/unit/gui/test_camera_thread.py index f9246367..70e6928d 100644 --- a/tests/unit/gui/test_camera_thread.py +++ b/tests/unit/gui/test_camera_thread.py @@ -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: diff --git a/tests/unit/utils/test_beamline_dispatch.py b/tests/unit/utils/test_beamline_dispatch.py new file mode 100644 index 00000000..fa53ef6f --- /dev/null +++ b/tests/unit/utils/test_beamline_dispatch.py @@ -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")