style: replace dunder attributes with normal 'private' attrs
This commit is contained in:
@@ -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")
|
||||
|
||||
|
||||
|
||||
+27
-27
@@ -64,16 +64,16 @@ class AareWrapper:
|
||||
|
||||
# Identity Forwarding (Optional now that mTLS is active, but safe to keep)
|
||||
self.client.default_headers["X-Shared-Password"] = os.getenv("AAREDB_SHARED_PASSWORD")
|
||||
self.__host = host
|
||||
self.__tell_api = aareDB.TellsRunnerApi(self.client)
|
||||
self.__sample_api = aareDB.SamplesRunnerApi(self.client)
|
||||
self.__proc_api = aareDB.ProcessingsRunnerApi(self.client)
|
||||
self.__raster_api = aareDB.GridscanRunnerApi(self.client)
|
||||
self.__bl = bl
|
||||
self._host = host
|
||||
self._tell_api = aareDB.TellsRunnerApi(self.client)
|
||||
self._sample_api = aareDB.SamplesRunnerApi(self.client)
|
||||
self._proc_api = aareDB.ProcessingsRunnerApi(self.client)
|
||||
self._raster_api = aareDB.GridscanRunnerApi(self.client)
|
||||
self._bl = bl
|
||||
|
||||
self.__ssl_ca_cert = configuration.ssl_ca_cert
|
||||
self.__cert_file = configuration.cert_file
|
||||
self.__key_file = configuration.key_file
|
||||
self._ssl_ca_cert = configuration.ssl_ca_cert
|
||||
self._cert_file = configuration.cert_file
|
||||
self._key_file = configuration.key_file
|
||||
|
||||
@log_timing(logger, "AareDB call")
|
||||
def set_pucks_beamline(self, input_list: List[PuckLoadedInfo]):
|
||||
@@ -84,8 +84,8 @@ class AareWrapper:
|
||||
puck_name=i.puck_name, segment=i.location.segment, puck_in_segment=i.location.pos
|
||||
)
|
||||
o.append(t)
|
||||
payload = SetTellPositionRequest(pucks=o, tell=self.__bl.value.upper())
|
||||
ret = self.__tell_api.set_tell_positions(set_tell_position_request=payload)
|
||||
payload = SetTellPositionRequest(pucks=o, tell=self._bl.value.upper())
|
||||
ret = self._tell_api.set_tell_positions(set_tell_position_request=payload)
|
||||
logger.debug(ret)
|
||||
|
||||
@log_timing(logger, "AareDB call")
|
||||
@@ -97,7 +97,7 @@ class AareWrapper:
|
||||
)
|
||||
|
||||
try:
|
||||
s.db_id = self.__sample_api.insert_sample(manual_sample).id
|
||||
s.db_id = self._sample_api.insert_sample(manual_sample).id
|
||||
except Exception as e:
|
||||
logger.error(f"Error inserting sample: {e}")
|
||||
|
||||
@@ -114,7 +114,7 @@ class AareWrapper:
|
||||
)
|
||||
return
|
||||
try:
|
||||
self.__sample_api.create_sample_event(
|
||||
self._sample_api.create_sample_event(
|
||||
sample_id=sample_id,
|
||||
sample_event_create=SampleEventCreate(event_type=event_type, comment=comment),
|
||||
)
|
||||
@@ -127,7 +127,7 @@ class AareWrapper:
|
||||
):
|
||||
_, buffer = cv2.imencode(".jpg", bgr_image)
|
||||
jpeg_bytes = io.BytesIO(buffer)
|
||||
url = f"{self.__host}/protected_router/sample_runner/{sample_id}/upload-images"
|
||||
url = f"{self._host}/protected_router/sample_runner/{sample_id}/upload-images"
|
||||
headers = {
|
||||
"accept": "application/json",
|
||||
"X-Shared-Password": os.getenv("AAREDB_SHARED_PASSWORD"),
|
||||
@@ -135,8 +135,8 @@ class AareWrapper:
|
||||
|
||||
request_kwargs = {
|
||||
"files": {"uploaded_file": (filename + ".jpg", jpeg_bytes, "image/jpeg")},
|
||||
"verify": self.__ssl_ca_cert,
|
||||
"cert": (self.__cert_file, self.__key_file),
|
||||
"verify": self._ssl_ca_cert,
|
||||
"cert": (self._cert_file, self._key_file),
|
||||
"headers": headers,
|
||||
}
|
||||
if message is not None:
|
||||
@@ -147,15 +147,15 @@ class AareWrapper:
|
||||
@log_timing(logger, "AareDB call")
|
||||
def upload_jpg(self, sample_id: int, filename: str, jpg_image, message: Optional[str] = None):
|
||||
logger.debug(f"jppg_image of type: {type(jpg_image)}")
|
||||
url = f"{self.__host}/protected_router/sample_runner/{sample_id}/upload-images"
|
||||
url = f"{self._host}/protected_router/sample_runner/{sample_id}/upload-images"
|
||||
headers = {
|
||||
"accept": "application/json",
|
||||
"X-Shared-Password": os.getenv("AAREDB_SHARED_PASSWORD"),
|
||||
}
|
||||
request_kwargs = {
|
||||
"files": {"uploaded_file": (filename + ".jpg", jpg_image, "image/jpeg")},
|
||||
"verify": self.__ssl_ca_cert,
|
||||
"cert": (self.__cert_file, self.__key_file),
|
||||
"verify": self._ssl_ca_cert,
|
||||
"cert": (self._cert_file, self._key_file),
|
||||
"headers": headers,
|
||||
}
|
||||
if message is not None:
|
||||
@@ -229,7 +229,7 @@ class AareWrapper:
|
||||
experiment_params_payload = ExperimentParametersCreate(
|
||||
type="standard", beamline_parameters=beamline_params, sample_id=s.db_id
|
||||
)
|
||||
self.__sample_api.create_experiment_parameters_for_sample(
|
||||
self._sample_api.create_experiment_parameters_for_sample(
|
||||
sample_id=s.db_id, experiment_parameters_create=experiment_params_payload
|
||||
)
|
||||
# logger.debug("Experiment parameters created:", response)
|
||||
@@ -291,7 +291,7 @@ class AareWrapper:
|
||||
experiment_params_payload = ExperimentParametersCreate(
|
||||
type="standard", beamline_parameters=beamline_params, sample_id=s.db_id
|
||||
)
|
||||
self.__sample_api.create_experiment_parameters_for_sample(
|
||||
self._sample_api.create_experiment_parameters_for_sample(
|
||||
sample_id=s.db_id, experiment_parameters_create=experiment_params_payload
|
||||
)
|
||||
# logger.info("Experiment parameters created:", response)
|
||||
@@ -319,7 +319,7 @@ class AareWrapper:
|
||||
return
|
||||
payload = payload_model.model_dump()
|
||||
|
||||
url = f"{self.__host}/protected_router/gridscan_runner/ingest"
|
||||
url = f"{self._host}/protected_router/gridscan_runner/ingest"
|
||||
headers = {
|
||||
"accept": "application/json",
|
||||
"X-Shared-Password": os.getenv("AAREDB_SHARED_PASSWORD"),
|
||||
@@ -330,8 +330,8 @@ class AareWrapper:
|
||||
headers=headers,
|
||||
data=json.dumps(payload),
|
||||
timeout=30,
|
||||
verify=self.__ssl_ca_cert,
|
||||
cert=(self.__cert_file, self.__key_file),
|
||||
verify=self._ssl_ca_cert,
|
||||
cert=(self._cert_file, self._key_file),
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
@@ -417,7 +417,7 @@ class AareWrapper:
|
||||
return
|
||||
payload = payload_model.model_dump()
|
||||
|
||||
url = f"{self.__host}/protected_router/scan_runner/ingest"
|
||||
url = f"{self._host}/protected_router/scan_runner/ingest"
|
||||
headers = {
|
||||
"accept": "application/json",
|
||||
"X-Shared-Password": os.getenv("AAREDB_SHARED_PASSWORD"),
|
||||
@@ -428,8 +428,8 @@ class AareWrapper:
|
||||
headers=headers,
|
||||
data=json.dumps(payload),
|
||||
timeout=30,
|
||||
verify=self.__ssl_ca_cert,
|
||||
cert=(self.__cert_file, self.__key_file),
|
||||
verify=self._ssl_ca_cert,
|
||||
cert=(self._cert_file, self._key_file),
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
|
||||
+168
-168
@@ -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")
|
||||
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}")
|
||||
|
||||
+414
-420
File diff suppressed because it is too large
Load Diff
+49
-49
@@ -35,25 +35,25 @@ class BeamlineDevices:
|
||||
logger.debug("initialising BEC worker")
|
||||
self.bec_worker = BECClientWorker(beamline)
|
||||
logger.debug("initialising BEC worker done")
|
||||
self.__smargon = smargon.Smargon(beamline)
|
||||
self._smargon = smargon.Smargon(beamline)
|
||||
self.exp_shutter = ExperimentalHutchShutter(beamline=self._beamline)
|
||||
# Personnel Safety System: gates whether the robot is allowed to move.
|
||||
self.pss = PssState(beamline=self._beamline)
|
||||
|
||||
# faster to define the dtz object here than in functions and then use
|
||||
self.__dtz = self.bec_worker.dev.det_z
|
||||
self._dtz = self.bec_worker.dev.det_z
|
||||
self.dtz_mod = cfg_get("daq.detector_distance_limit_modifier", 1.0)
|
||||
# TODO convert epics pvs to BEC
|
||||
self.__sample_cam = epicsAD(f"{BEAMLINE}-ES-MS:")
|
||||
self._sample_cam = epicsAD(f"{BEAMLINE}-ES-MS:")
|
||||
|
||||
self.__front_light = PredefinedPV(
|
||||
self._front_light = PredefinedPV(
|
||||
name="front_light",
|
||||
setpv=f"{BEAMLINE}-ES-FL:SET",
|
||||
getpv=f"{BEAMLINE}-ES-FL:SET",
|
||||
predefs={"off": 1.49, "half": 2.0, "max": 3.0},
|
||||
timeout=10.0,
|
||||
)
|
||||
self.__back_light = PredefinedPV(
|
||||
self._back_light = PredefinedPV(
|
||||
name="back_light",
|
||||
setpv=f"{BEAMLINE}-ES-BL:SET",
|
||||
getpv=f"{BEAMLINE}-ES-BL:SET",
|
||||
@@ -61,33 +61,33 @@ class BeamlineDevices:
|
||||
timeout=10.0,
|
||||
)
|
||||
|
||||
# self.__front_light = self.bec_worker.dev.fl_bright # need wrapper on bec_worker layer
|
||||
# self.__back_light = self.bec_worker.dev.bl_bright #need wrapper on bec_worker layer
|
||||
# self._front_light = self.bec_worker.dev.fl_bright # need wrapper on bec_worker layer
|
||||
# self._back_light = self.bec_worker.dev.bl_bright #need wrapper on bec_worker layer
|
||||
|
||||
self.__back_light_pos = EnumPV(
|
||||
self._back_light_pos = EnumPV(
|
||||
name="back_light_pos",
|
||||
setpv=f"{BEAMLINE}-ES-BL:POS-SET",
|
||||
getpv=f"{BEAMLINE}-ES-BL:POS-GET",
|
||||
timeout=10.0,
|
||||
)
|
||||
|
||||
self.__ringcurrent = self.bec_worker.ring_current
|
||||
self._ringcurrent = self.bec_worker.ring_current
|
||||
|
||||
self.__zoom = SetGetPV(
|
||||
self._zoom = SetGetPV(
|
||||
name="zoom", setpv=f"{BEAMLINE}-ES-MS:ZOOM.VAL", getpv=f"{BEAMLINE}-ES-MS:ZOOM.RBV"
|
||||
)
|
||||
|
||||
self.__cryojet_pos = EnumPV(
|
||||
self._cryojet_pos = EnumPV(
|
||||
name="cryojet_pos",
|
||||
setpv=f"{BEAMLINE}-ES-CS:POS-SET",
|
||||
getpv=f"{BEAMLINE}-ES-CS:POS-GET",
|
||||
timeout=10.0,
|
||||
)
|
||||
|
||||
self.__cryojet_x = MyMotor(f"{BEAMLINE}-ES-CS:TRX") # currently in is 5 out is 15?
|
||||
self._cryojet_x = MyMotor(f"{BEAMLINE}-ES-CS:TRX") # currently in is 5 out is 15?
|
||||
|
||||
self.__cryojet_temperature_get = PV(f"{BEAMLINE}-ES-CS:TEMP_RBV")
|
||||
self.__cryojet_temperature_set = PV(f"{BEAMLINE}-ES-CS:TEMP.VAL")
|
||||
self._cryojet_temperature_get = PV(f"{BEAMLINE}-ES-CS:TEMP_RBV")
|
||||
self._cryojet_temperature_set = PV(f"{BEAMLINE}-ES-CS:TEMP.VAL")
|
||||
|
||||
if self._beamline == MXBeamline.X06DA:
|
||||
self._transmission = SetGetPV(
|
||||
@@ -96,13 +96,13 @@ class BeamlineDevices:
|
||||
getpv=f"{BEAMLINE}-ES-BCFI:TRANSM-GET",
|
||||
)
|
||||
else:
|
||||
self.__transmission = SetGetPV(
|
||||
self._transmission = SetGetPV(
|
||||
name="transmission",
|
||||
setpv=f"{BEAMLINE}-ES-SSFI:TRANSM-SET",
|
||||
getpv=f"{BEAMLINE}-ES-SSFI:TRANSM-GET",
|
||||
)
|
||||
|
||||
self.__fast_shutter = PV(f"{BEAMLINE}-ES-SHUTTER:SET")
|
||||
self._fast_shutter = PV(f"{BEAMLINE}-ES-SHUTTER:SET")
|
||||
|
||||
self.magnet_position_sensor = PV(f"{BEAMLINE}-ES-DFS:CBOX-CMP1")
|
||||
self.magnet_position_sensor_readout = PV(f"{BEAMLINE}-ES-DFS:CBOX-USER1")
|
||||
@@ -134,7 +134,7 @@ class BeamlineDevices:
|
||||
def restart_smargon(self, simulated: bool = False) -> None:
|
||||
beamline = MXBeamline.SIMULATED if simulated else self._beamline
|
||||
logger.info(f"Restarting Smargon controller with simulated={simulated}")
|
||||
self.__smargon = smargon.Smargon(beamline)
|
||||
self._smargon = smargon.Smargon(beamline)
|
||||
|
||||
# Transmission
|
||||
@property
|
||||
@@ -152,38 +152,38 @@ class BeamlineDevices:
|
||||
# Lamp light
|
||||
@property
|
||||
def lamp_light(self) -> float:
|
||||
return self.__front_light.value
|
||||
return self._front_light.value
|
||||
|
||||
@lamp_light.setter
|
||||
def lamp_light(self, v: float):
|
||||
self.set_front_light(v, wait=False)
|
||||
|
||||
def set_front_light(self, v: float, /, wait: bool = True):
|
||||
self.__front_light.move(v, wait=wait)
|
||||
self._front_light.move(v, wait=wait)
|
||||
|
||||
# Back light
|
||||
@property
|
||||
def back_light(self) -> float:
|
||||
return self.__back_light.value
|
||||
return self._back_light.value
|
||||
|
||||
@back_light.setter
|
||||
def back_light(self, v: float):
|
||||
self.set_back_light(v, wait=False)
|
||||
|
||||
def set_back_light(self, v: float, /, wait: bool = True):
|
||||
self.__back_light.move(v, wait=wait)
|
||||
self._back_light.move(v, wait=wait)
|
||||
|
||||
# Zoom
|
||||
@property
|
||||
def zoom(self) -> float:
|
||||
return self.__zoom.value
|
||||
return self._zoom.value
|
||||
|
||||
@zoom.setter
|
||||
def zoom(self, value: float):
|
||||
self.set_zoom(value, wait=True)
|
||||
|
||||
def set_zoom(self, value: float, /, wait: bool = True):
|
||||
self.__zoom.move(value, wait=wait)
|
||||
self._zoom.move(value, wait=wait)
|
||||
|
||||
# Optics
|
||||
@property
|
||||
@@ -212,7 +212,7 @@ class BeamlineDevices:
|
||||
# Cryojet
|
||||
@property
|
||||
def cryojet_temp(self) -> float:
|
||||
return self.__cryojet_temperature_get.get()
|
||||
return self._cryojet_temperature_get.get()
|
||||
|
||||
def anneal(self, wait_time: float):
|
||||
logger.warning("Untested annealing procedure, use with caution")
|
||||
@@ -223,66 +223,66 @@ class BeamlineDevices:
|
||||
|
||||
@property
|
||||
def cryojet_pos(self) -> StagePositionEnum:
|
||||
return StagePositionEnum(self.__cryojet_pos.position.upper())
|
||||
return StagePositionEnum(self._cryojet_pos.position.upper())
|
||||
|
||||
@cryojet_pos.setter
|
||||
def cryojet_pos(self, value: StagePositionEnum):
|
||||
self.cryojet_pos_setter(value, wait=True)
|
||||
|
||||
def cryojet_pos_setter(self, value: StagePositionEnum, wait: bool = False):
|
||||
self.__cryojet_pos.move(value, wait=wait)
|
||||
self._cryojet_pos.move(value, wait=wait)
|
||||
|
||||
# Shutter
|
||||
@property
|
||||
def shutter(self) -> bool:
|
||||
return self.__fast_shutter.get()
|
||||
return self._fast_shutter.get()
|
||||
|
||||
@shutter.setter
|
||||
def shutter(self, opened: bool):
|
||||
self.__fast_shutter.put(opened)
|
||||
self._fast_shutter.put(opened)
|
||||
|
||||
# Sample camera
|
||||
@property
|
||||
def samcam_settings(self) -> SampleCameraSettings:
|
||||
return SampleCameraSettings(
|
||||
gain=self.__sample_cam.gain_rbv.value, exposure=self.__sample_cam.expo_rbv.value
|
||||
gain=self._sample_cam.gain_rbv.value, exposure=self._sample_cam.expo_rbv.value
|
||||
)
|
||||
|
||||
@samcam_settings.setter
|
||||
def samcam_settings(self, settings: SampleCameraSettings):
|
||||
self.__sample_cam.setup(settings.gain, settings.exposure)
|
||||
self._sample_cam.setup(settings.gain, settings.exposure)
|
||||
|
||||
@log_timing(logger, "auto exposure of camera")
|
||||
def samcam_auto(self, state: AutoEnum):
|
||||
self.__sample_cam.set_auto(state)
|
||||
self._sample_cam.set_auto(state)
|
||||
|
||||
def samcam_frame_id(self) -> int:
|
||||
"""
|
||||
Camera UniqueId for the last produced frame (monotonic counter from AreaDetector).
|
||||
"""
|
||||
return int(self.__sample_cam.uid.get())
|
||||
return int(self._sample_cam.uid.get())
|
||||
|
||||
# Detector Z
|
||||
@property
|
||||
def dtz(self) -> float:
|
||||
return self.__dtz.read()["det_z"]["value"]
|
||||
return self._dtz.read()["det_z"]["value"]
|
||||
|
||||
@dtz.setter
|
||||
def dtz(self, value: float):
|
||||
self.set_dtz(value, wait=True)
|
||||
|
||||
def set_dtz(self, value: float, /, wait: bool = True):
|
||||
if value < self.__dtz.low_limit:
|
||||
if value < self._dtz.low_limit:
|
||||
# raise ValueError(f"Detector distance cannot be less than {self.detector_distance_minimum} mm")
|
||||
logger.warning(
|
||||
f"Requested detector distance {value} is less than minimum: {self.__dtz.low_limit}, setting to minimum"
|
||||
f"Requested detector distance {value} is less than minimum: {self._dtz.low_limit}, setting to minimum"
|
||||
)
|
||||
value = self.__dtz.low_limit + self.dtz_mod
|
||||
if value > self.__dtz.high_limit:
|
||||
value = self._dtz.low_limit + self.dtz_mod
|
||||
if value > self._dtz.high_limit:
|
||||
logger.warning(
|
||||
f"Requested detector distance {value} is greater than maximum: {self.__dtz.high_limit}, setting to maximum"
|
||||
f"Requested detector distance {value} is greater than maximum: {self._dtz.high_limit}, setting to maximum"
|
||||
)
|
||||
value = self.__dtz.high_limit - self.dtz_mod
|
||||
value = self._dtz.high_limit - self.dtz_mod
|
||||
if wait:
|
||||
status = self.bec_worker.det_z(value, timeout=60)
|
||||
return status
|
||||
@@ -292,13 +292,13 @@ class BeamlineDevices:
|
||||
|
||||
@property
|
||||
def dtz_low(self) -> float:
|
||||
logger.info(f"dtz_low: {self.__dtz.low_limit}")
|
||||
return self.__dtz.low_limit
|
||||
logger.info(f"dtz_low: {self._dtz.low_limit}")
|
||||
return self._dtz.low_limit
|
||||
|
||||
@property
|
||||
def dtz_high(self) -> float:
|
||||
logger.info(f"dtz_high: {self.__dtz.high_limit}")
|
||||
return self.__dtz.high_limit
|
||||
logger.info(f"dtz_high: {self._dtz.high_limit}")
|
||||
return self._dtz.high_limit
|
||||
|
||||
@property
|
||||
def aerotech_pos(self) -> AerotechCoordinate:
|
||||
@@ -330,26 +330,26 @@ class BeamlineDevices:
|
||||
# Smargon goniometer
|
||||
@property
|
||||
def smargon_pos(self) -> SmargonCoordinate:
|
||||
return self.__smargon.readback
|
||||
return self._smargon.readback
|
||||
|
||||
def set_smargon_pos(self, pos: SmargonCoordinate, /, wait: bool = True):
|
||||
self.__smargon.target = pos
|
||||
self._smargon.target = pos
|
||||
|
||||
@smargon_pos.setter
|
||||
def smargon_pos(self, pos: SmargonCoordinate):
|
||||
self.set_smargon_pos(pos, wait=True)
|
||||
|
||||
def smargon_wait(self, timeout: float = 10.0):
|
||||
self.__smargon.wait(timeout=timeout)
|
||||
self._smargon.wait(timeout=timeout)
|
||||
|
||||
def smargon_move_home(self):
|
||||
self.__smargon.move_home(wait=True)
|
||||
self._smargon.move_home(wait=True)
|
||||
|
||||
def smargon_aerotech_wait(self):
|
||||
self.__smargon.wait_aerotech(timeout=10.0)
|
||||
self._smargon.wait_aerotech(timeout=10.0)
|
||||
|
||||
def smargon_initialize(self):
|
||||
self.__smargon.initialize()
|
||||
self._smargon.initialize()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -52,10 +52,10 @@ class MlBox:
|
||||
Optional injected inference wrapper for tests.
|
||||
If None, the production wrapper is created lazily.
|
||||
"""
|
||||
self.__beamline = bl
|
||||
self._beamline = bl
|
||||
|
||||
if wrapper is not None:
|
||||
self.__wrapper = wrapper
|
||||
self._wrapper = wrapper
|
||||
return
|
||||
|
||||
if bl == MXBeamline.SIMULATED:
|
||||
@@ -64,13 +64,13 @@ class MlBox:
|
||||
elif bl == MXBeamline.X06DA:
|
||||
from aarecommon.lc_infer_wrapper import AareLCInferWrapper
|
||||
|
||||
self.__wrapper = AareLCInferWrapper(bl)
|
||||
self._wrapper = AareLCInferWrapper(bl)
|
||||
|
||||
elif bl == MXBeamline.X10SA:
|
||||
# Lazy import keeps module importable without external client installed
|
||||
from aarecommon.lc_infer_wrapper import AareLCInferWrapper
|
||||
|
||||
self.__wrapper = AareLCInferWrapper(bl)
|
||||
self._wrapper = AareLCInferWrapper(bl)
|
||||
|
||||
elif bl == MXBeamline.X06SA:
|
||||
raise NotImplementedError(f"MLBox bundle mode not implemented for {bl}")
|
||||
@@ -120,7 +120,7 @@ class MlBox:
|
||||
last_error = None
|
||||
for attempt in range(1, self.RETRY_COUNT + 1):
|
||||
try:
|
||||
bundle = self.__wrapper.get_latest_prediction_bundle()
|
||||
bundle = self._wrapper.get_latest_prediction_bundle()
|
||||
logger.debug(f"Fetched prediction bundle on attempt {attempt}/{self.RETRY_COUNT}")
|
||||
return bundle
|
||||
except Exception as e:
|
||||
|
||||
+12
-12
@@ -686,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
|
||||
|
||||
|
||||
@@ -26,27 +26,27 @@ 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(
|
||||
self, coord: AerotechCoordinate, wait: bool = False, incremental: bool = False
|
||||
@@ -69,24 +69,24 @@ class AerotechController(object):
|
||||
|
||||
def cancel(self):
|
||||
try:
|
||||
self.__api.cancel_post()
|
||||
self._api.cancel_post()
|
||||
except Exception as e:
|
||||
raise AerotechCommunicationError(
|
||||
"Aerotech cancel failed",
|
||||
endpoint="cancel_post",
|
||||
base_url=self.__base,
|
||||
base_url=self._base,
|
||||
operation="POST",
|
||||
) from e
|
||||
|
||||
def is_idle(self) -> bool:
|
||||
try:
|
||||
status = self.__api.status_get()
|
||||
status = self._api.status_get()
|
||||
return status.state == "Idle"
|
||||
except Exception as e:
|
||||
raise AerotechCommunicationError(
|
||||
"Aerotech status check failed",
|
||||
endpoint="status_get",
|
||||
base_url=self.__base,
|
||||
base_url=self._base,
|
||||
operation="GET",
|
||||
) from e
|
||||
|
||||
@@ -97,36 +97,36 @@ class AerotechController(object):
|
||||
)
|
||||
|
||||
def status(self) -> Status:
|
||||
if self.__simulated:
|
||||
if self._simulated:
|
||||
return Status(
|
||||
state=Status.State.IDLE,
|
||||
x=AxisStatus(
|
||||
pos=self.__pos.x,
|
||||
vel=self.__vel,
|
||||
pos=self._pos.x,
|
||||
vel=self._vel,
|
||||
enabled=False,
|
||||
homed=False,
|
||||
moving=False,
|
||||
fault=False,
|
||||
),
|
||||
y=AxisStatus(
|
||||
pos=self.__pos.y,
|
||||
vel=self.__vel,
|
||||
pos=self._pos.y,
|
||||
vel=self._vel,
|
||||
enabled=False,
|
||||
homed=False,
|
||||
moving=False,
|
||||
fault=False,
|
||||
),
|
||||
z=AxisStatus(
|
||||
pos=self.__pos.z,
|
||||
vel=self.__vel,
|
||||
pos=self._pos.z,
|
||||
vel=self._vel,
|
||||
enabled=False,
|
||||
homed=False,
|
||||
moving=False,
|
||||
fault=False,
|
||||
),
|
||||
u=AxisStatus(
|
||||
pos=self.__pos.u,
|
||||
vel=self.__vel,
|
||||
pos=self._pos.u,
|
||||
vel=self._vel,
|
||||
enabled=False,
|
||||
homed=False,
|
||||
moving=False,
|
||||
@@ -134,52 +134,52 @@ class AerotechController(object):
|
||||
),
|
||||
)
|
||||
try:
|
||||
return self.__api.status_get()
|
||||
return self._api.status_get()
|
||||
except Exception as e:
|
||||
raise AerotechCommunicationError(
|
||||
"Aerotech status request failed",
|
||||
endpoint="status_get",
|
||||
base_url=self.__base,
|
||||
base_url=self._base,
|
||||
operation="GET",
|
||||
) from e
|
||||
|
||||
def move_home(self, wait: bool = True, incremental: bool = False):
|
||||
if self.__simulated:
|
||||
self.__pos = AEROTECH_HOME
|
||||
return self.__pos
|
||||
if self._simulated:
|
||||
self._pos = AEROTECH_HOME
|
||||
return self._pos
|
||||
return self.position(AEROTECH_HOME, wait=wait, incremental=incremental)
|
||||
|
||||
def home_aerotech(self):
|
||||
try:
|
||||
return self.__api.home_post()
|
||||
return self._api.home_post()
|
||||
except Exception as e:
|
||||
raise AerotechCommunicationError(
|
||||
"Aerotech home failed", endpoint="home_post", base_url=self.__base, operation="POST"
|
||||
"Aerotech home failed", endpoint="home_post", base_url=self._base, operation="POST"
|
||||
) from e
|
||||
|
||||
def wait_till_done(self, timeout=60):
|
||||
try:
|
||||
return self.__api.wait_till_done_post(timeout=timeout)
|
||||
return self._api.wait_till_done_post(timeout=timeout)
|
||||
except Exception as e:
|
||||
raise AerotechCommunicationError(
|
||||
"Aerotech wait_till_done failed",
|
||||
endpoint="wait_till_done_post",
|
||||
base_url=self.__base,
|
||||
base_url=self._base,
|
||||
operation="POST",
|
||||
) from e
|
||||
|
||||
def position(self, target: AerotechCoordinate, /, wait: bool = True, incremental: bool = False):
|
||||
if self.__simulated:
|
||||
return self.__pos
|
||||
if self._simulated:
|
||||
return self._pos
|
||||
|
||||
payload = self.__make_aerotech_target(target, wait=wait, incremental=incremental)
|
||||
payload = self._make_aerotech_target(target, wait=wait, incremental=incremental)
|
||||
try:
|
||||
return self.__api.position_post(payload)
|
||||
return self._api.position_post(payload)
|
||||
except Exception as e:
|
||||
raise AerotechCommunicationError(
|
||||
"Aerotech position move failed",
|
||||
endpoint="position_post",
|
||||
base_url=self.__base,
|
||||
base_url=self._base,
|
||||
operation="POST",
|
||||
) from e
|
||||
|
||||
@@ -196,16 +196,16 @@ class AerotechController(object):
|
||||
start_pos_deg=start_pos_deg,
|
||||
run_async=run_async,
|
||||
)
|
||||
if self.__simulated:
|
||||
if self._simulated:
|
||||
return payload
|
||||
|
||||
try:
|
||||
return self.__api.rotation_scan_post(payload)
|
||||
return self._api.rotation_scan_post(payload)
|
||||
except Exception as e:
|
||||
raise AerotechCommunicationError(
|
||||
"Aerotech rotation scan failed",
|
||||
endpoint="rotation_scan_post",
|
||||
base_url=self.__base,
|
||||
base_url=self._base,
|
||||
operation="POST",
|
||||
) from e
|
||||
|
||||
@@ -226,15 +226,15 @@ class AerotechController(object):
|
||||
time_sec=time_sec,
|
||||
run_async=run_async,
|
||||
)
|
||||
if self.__simulated:
|
||||
if self._simulated:
|
||||
return payload
|
||||
try:
|
||||
return self.__api.grid_scan_post(payload)
|
||||
return self._api.grid_scan_post(payload)
|
||||
except Exception as e:
|
||||
raise AerotechCommunicationError(
|
||||
"Aerotech grid scan failed",
|
||||
endpoint="grid_scan_post",
|
||||
base_url=self.__base,
|
||||
base_url=self._base,
|
||||
operation="POST",
|
||||
) from e
|
||||
|
||||
@@ -253,16 +253,16 @@ class AerotechController(object):
|
||||
steps=steps,
|
||||
run_async=run_async,
|
||||
)
|
||||
if self.__simulated:
|
||||
if self._simulated:
|
||||
return payload
|
||||
try:
|
||||
logger.info("sending screening scan request to aerotech")
|
||||
return self.__api.screening_post(payload)
|
||||
return self._api.screening_post(payload)
|
||||
except Exception as e:
|
||||
raise AerotechCommunicationError(
|
||||
"Aerotech screening scan failed",
|
||||
endpoint="screening_post",
|
||||
base_url=self.__base,
|
||||
base_url=self._base,
|
||||
operation="POST",
|
||||
) from e
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ 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()
|
||||
|
||||
@@ -91,12 +91,12 @@ class BECClientWorker:
|
||||
print(self.dev.keys())
|
||||
self.scans = self.client.scans
|
||||
self.macros = self.client.macros
|
||||
self.__load_user_macros()
|
||||
print(self.__list_all_macros())
|
||||
self._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)
|
||||
@@ -105,22 +105,22 @@ class BECClientWorker:
|
||||
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._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}")
|
||||
|
||||
@@ -298,7 +298,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")
|
||||
@@ -318,7 +318,7 @@ 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")
|
||||
|
||||
@@ -348,7 +348,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:
|
||||
@@ -460,7 +460,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 +475,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"""
|
||||
@@ -520,11 +520,11 @@ class BECClientWorker:
|
||||
|
||||
@property
|
||||
def zoom(self):
|
||||
return self.__zoom.position
|
||||
return self._zoom.position
|
||||
|
||||
@zoom.setter
|
||||
def zoom(self, value: float):
|
||||
self.scans.umv(self.__zoom, value, relative=False)
|
||||
self.scans.umv(self._zoom, value, relative=False)
|
||||
|
||||
@property
|
||||
def ring_current(self) -> float:
|
||||
|
||||
@@ -5,12 +5,12 @@ from epics import PV
|
||||
class ExperimentalHutchShutter:
|
||||
def __init__(self, beamline: MXBeamline):
|
||||
BEAMLINE = beamline.value.upper()
|
||||
self.__close = PV(f"{BEAMLINE}-EH1-PSYS:SH-A-CLOSE-SET") # 1, 0
|
||||
self.__open = PV(f"{BEAMLINE}-EH1-PSYS:SH-A-OPEN-SET")
|
||||
self.__state = PV(f"{BEAMLINE}-OP-PSH1-EMLS-0010:OPEN")
|
||||
self._close = PV(f"{BEAMLINE}-EH1-PSYS:SH-A-CLOSE-SET") # 1, 0
|
||||
self._open = PV(f"{BEAMLINE}-EH1-PSYS:SH-A-OPEN-SET")
|
||||
self._state = PV(f"{BEAMLINE}-OP-PSH1-EMLS-0010:OPEN")
|
||||
|
||||
def state(self):
|
||||
state = self.__state.get()
|
||||
state = self._state.get()
|
||||
if state == "Open" or state == 1:
|
||||
return True
|
||||
elif state == "Not Open" or state == 0:
|
||||
@@ -20,11 +20,11 @@ class ExperimentalHutchShutter:
|
||||
return False
|
||||
|
||||
def open(self):
|
||||
self.__open.put(0)
|
||||
self.__open.put(1)
|
||||
# print(self.__open.get())
|
||||
self.__open.put(0)
|
||||
self._open.put(0)
|
||||
self._open.put(1)
|
||||
# print(self._open.get())
|
||||
self._open.put(0)
|
||||
|
||||
def close(self):
|
||||
self.__close.put(1)
|
||||
self.__close.put(0)
|
||||
self._close.put(1)
|
||||
self._close.put(0)
|
||||
|
||||
@@ -11,18 +11,18 @@ class FilterTransmission:
|
||||
self._fail_on_timeout = fail_on_timeout
|
||||
self._timeout = timeout
|
||||
|
||||
self.__set = PV(f"{BEAMLINE}-ES-FI:TRANSM-SET")
|
||||
self.__get = PV(f"{BEAMLINE}-ES-FI:TRANSM-GET")
|
||||
self.__done = PV(f"{BEAMLINE}-ES-FI:DONE")
|
||||
self._set = PV(f"{BEAMLINE}-ES-FI:TRANSM-SET")
|
||||
self._get = PV(f"{BEAMLINE}-ES-FI:TRANSM-GET")
|
||||
self._done = PV(f"{BEAMLINE}-ES-FI:DONE")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<FilterTransmission: {self.__set.get():.4f}>"
|
||||
return f"<FilterTransmission: {self._set.get():.4f}>"
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.__get.get():.4f}"
|
||||
return f"{self._get.get():.4f}"
|
||||
|
||||
def is_done(self):
|
||||
return 1 == int(self.__done.value)
|
||||
return 1 == int(self._done.value)
|
||||
|
||||
def is_busy(self):
|
||||
return not self.is_done()
|
||||
@@ -33,7 +33,7 @@ class FilterTransmission:
|
||||
raise RuntimeError(
|
||||
f"requested transmission ({val:.4f}) is out of bounds --- valid range : 0 <= trans <= 1.0"
|
||||
)
|
||||
self.__set.put(val)
|
||||
self._set.put(val)
|
||||
poll(0.2)
|
||||
if wait:
|
||||
self.wait()
|
||||
@@ -41,7 +41,7 @@ class FilterTransmission:
|
||||
def get(self) -> float | None:
|
||||
if self.is_busy():
|
||||
return None
|
||||
return round(self.__get.value, 5)
|
||||
return round(self._get.value, 5)
|
||||
|
||||
def wait(self):
|
||||
timeisup = False
|
||||
|
||||
@@ -11,38 +11,38 @@ class Fluorimeter(object):
|
||||
def __init__(self, beamline: MXBeamline, **kwargs):
|
||||
BEAMLINE = beamline.value.upper()
|
||||
|
||||
self.__start = PV(f"{BEAMLINE}-ES-SiD:mca1Start") # 0 done ,1 start
|
||||
self.__stop = PV(f"{BEAMLINE}-ES-SiD:mca1Stop") # 0 done ,1 stop
|
||||
self.__erase_and_start = PV(f"{BEAMLINE}-ES-SiD:mca1EraseStart") # 0 done,1 start
|
||||
self.__erase = PV(f"{BEAMLINE}-ES-SiD:mca1Erase") # 0 done,1 erase
|
||||
self._start = PV(f"{BEAMLINE}-ES-SiD:mca1Start") # 0 done ,1 start
|
||||
self._stop = PV(f"{BEAMLINE}-ES-SiD:mca1Stop") # 0 done ,1 stop
|
||||
self._erase_and_start = PV(f"{BEAMLINE}-ES-SiD:mca1EraseStart") # 0 done,1 start
|
||||
self._erase = PV(f"{BEAMLINE}-ES-SiD:mca1Erase") # 0 done,1 erase
|
||||
|
||||
self.__preset_mode = PV(f"{BEAMLINE}-ES-SiD:dxp1:PresetMode") # set mode
|
||||
self.__status = PV(f"{BEAMLINE}-ES-SiD:mca1.ACQG") # 0 done, 1 acquire
|
||||
self._preset_mode = PV(f"{BEAMLINE}-ES-SiD:dxp1:PresetMode") # set mode
|
||||
self._status = PV(f"{BEAMLINE}-ES-SiD:mca1.ACQG") # 0 done, 1 acquire
|
||||
|
||||
self.__real_time = PV(f"{BEAMLINE}-ES-SiD:mca1.PRTM") # float
|
||||
self.__live_time = PV(f"{BEAMLINE}-ES-SiD:mca1.PLTM") # float
|
||||
self.__elapsed_real_time = PV(f"{BEAMLINE}-ES-SiD:mca1.ERTM") # float
|
||||
self.__elapsed_live_time = PV(f"{BEAMLINE}-ES-SiD:mca1.ELTM") # float
|
||||
self.__elapsed_trigger_live_time = PV(f"{BEAMLINE}-ES-SiD:dxp1:ElapsedTriggerLiveTime")
|
||||
self._real_time = PV(f"{BEAMLINE}-ES-SiD:mca1.PRTM") # float
|
||||
self._live_time = PV(f"{BEAMLINE}-ES-SiD:mca1.PLTM") # float
|
||||
self._elapsed_real_time = PV(f"{BEAMLINE}-ES-SiD:mca1.ERTM") # float
|
||||
self._elapsed_live_time = PV(f"{BEAMLINE}-ES-SiD:mca1.ELTM") # float
|
||||
self._elapsed_trigger_live_time = PV(f"{BEAMLINE}-ES-SiD:dxp1:ElapsedTriggerLiveTime")
|
||||
|
||||
self.__instant_dead_time = PV(f"{BEAMLINE}-ES-SiD:mca1.IDTIM")
|
||||
self.__average_dead_time = PV(f"{BEAMLINE}-ES-SiD:mca1.DTIM")
|
||||
self._instant_dead_time = PV(f"{BEAMLINE}-ES-SiD:mca1.IDTIM")
|
||||
self._average_dead_time = PV(f"{BEAMLINE}-ES-SiD:mca1.DTIM")
|
||||
|
||||
self.__poll_time_rbv = PV(f"{BEAMLINE}-ES-SiD:PollTime_RBV")
|
||||
self.__poll_time = PV(f"{BEAMLINE}-ES-SiD:PollTime")
|
||||
self._poll_time_rbv = PV(f"{BEAMLINE}-ES-SiD:PollTime_RBV")
|
||||
self._poll_time = PV(f"{BEAMLINE}-ES-SiD:PollTime")
|
||||
|
||||
self.__mca = PV(f"{BEAMLINE}-ES-SiD:mca1")
|
||||
self.__data = PV(f"{BEAMLINE}-ES-SiD:mca1.VAL")
|
||||
self.__background = PV(f"{BEAMLINE}-ES-SiD:mca1.BG")
|
||||
self._mca = PV(f"{BEAMLINE}-ES-SiD:mca1")
|
||||
self._data = PV(f"{BEAMLINE}-ES-SiD:mca1.VAL")
|
||||
self._background = PV(f"{BEAMLINE}-ES-SiD:mca1.BG")
|
||||
|
||||
self.__saveFile = PV(f"{BEAMLINE}-ES-SiD:SaveSystemFile")
|
||||
self.__saveFile_name = PV(f"{BEAMLINE}-ES-SiD:SaveSystem")
|
||||
self.__saveFile_rbv = PV(f"{BEAMLINE}-ES-SiD:SaveSystem_RBV") # 1 ssave, 0 done
|
||||
self.__roi_1 = self.get_roi(BEAMLINE, 1) # currently SiEsc
|
||||
self.__roi_2 = self.get_roi(BEAMLINE, 2) # currently MnKa
|
||||
self._saveFile = PV(f"{BEAMLINE}-ES-SiD:SaveSystemFile")
|
||||
self._saveFile_name = PV(f"{BEAMLINE}-ES-SiD:SaveSystem")
|
||||
self._saveFile_rbv = PV(f"{BEAMLINE}-ES-SiD:SaveSystem_RBV") # 1 ssave, 0 done
|
||||
self._roi_1 = self.get_roi(BEAMLINE, 1) # currently SiEsc
|
||||
self._roi_2 = self.get_roi(BEAMLINE, 2) # currently MnKa
|
||||
|
||||
self.__calibration_offset = PV(f"{BEAMLINE}-ES-SiD:mca1.CALO")
|
||||
self.__calibration_slope = PV(f"{BEAMLINE}-ES-SiD:mca1.CALS")
|
||||
self._calibration_offset = PV(f"{BEAMLINE}-ES-SiD:mca1.CALO")
|
||||
self._calibration_slope = PV(f"{BEAMLINE}-ES-SiD:mca1.CALS")
|
||||
|
||||
def get_roi(self, bl, roi_num: int):
|
||||
if roi_num not in [1, 2]:
|
||||
@@ -52,15 +52,15 @@ class Fluorimeter(object):
|
||||
|
||||
def start_acquisition(self, erase: bool = False):
|
||||
if erase:
|
||||
self.__erase_and_start.put(1)
|
||||
self._erase_and_start.put(1)
|
||||
else:
|
||||
self.__start.put(1)
|
||||
self._start.put(1)
|
||||
|
||||
def stop_acquisition(self):
|
||||
self.__stop.put(1)
|
||||
self._stop.put(1)
|
||||
|
||||
def erase(self):
|
||||
self.__erase.put(1)
|
||||
self._erase.put(1)
|
||||
|
||||
def poll_data(self, timeout_s: float = 30.0):
|
||||
timeout = time.time() + timeout_s
|
||||
@@ -68,17 +68,17 @@ class Fluorimeter(object):
|
||||
if time.time() > timeout:
|
||||
self.stop_acquisition()
|
||||
raise TimeoutError("timeout waiting for data to be acquired.")
|
||||
self.__mca.get()
|
||||
self._mca.get()
|
||||
poll(0.1)
|
||||
|
||||
def get_current_data(self):
|
||||
return self.__data.get()
|
||||
return self._data.get()
|
||||
|
||||
def get_current_background(self):
|
||||
return self.__background.get()
|
||||
return self._background.get()
|
||||
|
||||
def check_status(self):
|
||||
return self.__status.get()
|
||||
return self._status.get()
|
||||
|
||||
def check_status_done(self):
|
||||
if self.check_status() == 0:
|
||||
@@ -107,18 +107,18 @@ class Fluorimeter(object):
|
||||
if mode not in [0, 1, 2, 3, 4]:
|
||||
raise ValueError(f"Invalid preset mode: {mode}")
|
||||
try:
|
||||
self.__preset_mode.put(mode)
|
||||
self._preset_mode.put(mode)
|
||||
except Exception as e:
|
||||
logger.error(f"Error setting preset mode to {mode}: {e}")
|
||||
|
||||
def save_file(self, filename: str | None = None, timeout_s: float = 30.0):
|
||||
if not filename:
|
||||
filename = f"KETEK_{time.strftime('%Y%m%d_%H%M%S')}.txt"
|
||||
self.__saveFile_name.put(filename)
|
||||
self._saveFile_name.put(filename)
|
||||
try:
|
||||
self.__saveFile.put(1)
|
||||
self._saveFile.put(1)
|
||||
timeout = time.time() + timeout_s
|
||||
while self.__saveFile_rbv.get() == 1:
|
||||
while self._saveFile_rbv.get() == 1:
|
||||
if time.time() > timeout:
|
||||
raise TimeoutError("Timed out waiting for save to complete")
|
||||
poll(0.1)
|
||||
@@ -127,47 +127,47 @@ class Fluorimeter(object):
|
||||
|
||||
@property
|
||||
def real_time(self):
|
||||
return self.__real_time.get()
|
||||
return self._real_time.get()
|
||||
|
||||
@real_time.setter
|
||||
def real_time(self, value):
|
||||
self.__real_time.put(value)
|
||||
self._real_time.put(value)
|
||||
|
||||
@property
|
||||
def live_time(self):
|
||||
return self.__live_time.get()
|
||||
return self._live_time.get()
|
||||
|
||||
@live_time.setter
|
||||
def live_time(self, value):
|
||||
self.__live_time.put(value)
|
||||
self._live_time.put(value)
|
||||
|
||||
def elapsed_real_time(self):
|
||||
return self.__elapsed_real_time.get()
|
||||
return self._elapsed_real_time.get()
|
||||
|
||||
def elapsed_live_time(self):
|
||||
return self.__elapsed_live_time.get()
|
||||
return self._elapsed_live_time.get()
|
||||
|
||||
def elapsed_trigger_live_time(self):
|
||||
return self.__elapsed_trigger_live_time.get()
|
||||
return self._elapsed_trigger_live_time.get()
|
||||
|
||||
def instant_dead_time(self):
|
||||
return self.__instant_dead_time.get()
|
||||
return self._instant_dead_time.get()
|
||||
|
||||
def average_dead_time(self):
|
||||
return self.__average_dead_time.get()
|
||||
return self._average_dead_time.get()
|
||||
|
||||
@property
|
||||
def slope(self):
|
||||
return self.__calibration_slope.get()
|
||||
return self._calibration_slope.get()
|
||||
|
||||
@property
|
||||
def offset(self):
|
||||
return self.__calibration_offset.get()
|
||||
return self._calibration_offset.get()
|
||||
|
||||
@property
|
||||
def poll_time(self):
|
||||
return self.__poll_time_rbv.get()
|
||||
return self._poll_time_rbv.get()
|
||||
|
||||
@poll_time.setter
|
||||
def poll_time(self, value):
|
||||
self.__poll_time.put(value)
|
||||
self._poll_time.put(value)
|
||||
|
||||
+24
-24
@@ -22,16 +22,16 @@ 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":
|
||||
self._client = None
|
||||
self._api = None
|
||||
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 +56,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,14 +70,14 @@ 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(
|
||||
@@ -200,9 +200,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 +219,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 +241,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 +256,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 +270,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 +301,7 @@ class JFJochWrapper:
|
||||
last_error = None
|
||||
for _ in range(3):
|
||||
try:
|
||||
return self.__api.image_buffer_image_jpeg_get(
|
||||
return self._api.image_buffer_image_jpeg_get(
|
||||
id=image_id,
|
||||
show_spots=show_spots,
|
||||
show_res_est=show_res_est,
|
||||
|
||||
@@ -24,11 +24,11 @@ class PssState:
|
||||
self._simulated = beamline == MXBeamline.SIMULATED
|
||||
BEAMLINE = beamline.value.upper()
|
||||
if self._simulated:
|
||||
self.__prohibited = None
|
||||
self.__alarm = None
|
||||
self._prohibited = None
|
||||
self._alarm = None
|
||||
else:
|
||||
self.__prohibited = PV(f"{BEAMLINE}-EH1-PSYS:PROHIBITED-STATE")
|
||||
self.__alarm = PV(f"{BEAMLINE}-EH1-PSYS:ALARM-STATE")
|
||||
self._prohibited = PV(f"{BEAMLINE}-EH1-PSYS:PROHIBITED-STATE")
|
||||
self._alarm = PV(f"{BEAMLINE}-EH1-PSYS:ALARM-STATE")
|
||||
|
||||
def is_prohibited(self) -> bool:
|
||||
"""True when the hutch is interlocked so the robot is allowed to move.
|
||||
@@ -39,7 +39,7 @@ class PssState:
|
||||
"""
|
||||
if self._simulated:
|
||||
return True
|
||||
value = self.__prohibited.get()
|
||||
value = self._prohibited.get()
|
||||
if value is None:
|
||||
logger.warning(
|
||||
"PROHIBITED-STATE PV returned no value; treating hutch as not prohibited"
|
||||
@@ -55,7 +55,7 @@ class PssState:
|
||||
"""True when ``ALARM-STATE`` is non-zero (a safety alarm is active)."""
|
||||
if self._simulated:
|
||||
return False
|
||||
value = self.__alarm.get()
|
||||
value = self._alarm.get()
|
||||
if value is None:
|
||||
return False
|
||||
if isinstance(value, str):
|
||||
|
||||
+29
-29
@@ -21,31 +21,31 @@ class Smargon(object):
|
||||
|
||||
def __init__(self, bl: MXBeamline):
|
||||
if bl == MXBeamline.X06DA:
|
||||
self.__simulated = False
|
||||
self.__base = cfg_get("daq.hardware.smargon_url", "http://x06da-smargopolo.psi.ch:3000")
|
||||
self._simulated = False
|
||||
self._base = cfg_get("daq.hardware.smargon_url", "http://x06da-smargopolo.psi.ch:3000")
|
||||
elif bl == MXBeamline.X10SA:
|
||||
self.__simulated = False
|
||||
self.__base = cfg_get("daq.hardware.smargon_url", "http://x10sa-smargopolo.psi.ch:3000")
|
||||
self._simulated = False
|
||||
self._base = cfg_get("daq.hardware.smargon_url", "http://x10sa-smargopolo.psi.ch:3000")
|
||||
elif bl == MXBeamline.X06SA:
|
||||
raise NotImplementedError("Not implemented smargon url for X06SA")
|
||||
elif bl == MXBeamline.SIMULATED:
|
||||
self.__simulated = True
|
||||
self._simulated = True
|
||||
else:
|
||||
raise Exception("unknown beamline")
|
||||
self.__pos = self.SMARGON_HOME
|
||||
self.__pos_aero = self.AERO_HOME
|
||||
self._pos = self.SMARGON_HOME
|
||||
self._pos_aero = self.AERO_HOME
|
||||
|
||||
def gonget(self, thing: str) -> dict:
|
||||
"""issue a GET for some API component on the smargopolo server
|
||||
short hand for goniometer get"""
|
||||
cmd = f"{self.__base}/{thing}"
|
||||
cmd = f"{self._base}/{thing}"
|
||||
try:
|
||||
r = requests.get(cmd, timeout=2.0)
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise SmargonCommunicationError(
|
||||
f"Smargon GET failed for '{thing}'",
|
||||
endpoint=thing,
|
||||
base_url=self.__base,
|
||||
base_url=self._base,
|
||||
operation="GET",
|
||||
) from e
|
||||
|
||||
@@ -53,7 +53,7 @@ class Smargon(object):
|
||||
raise SmargonCommunicationError(
|
||||
f"Smargon GET returned HTTP {r.status_code} for '{thing}': {r.reason}",
|
||||
endpoint=thing,
|
||||
base_url=self.__base,
|
||||
base_url=self._base,
|
||||
operation="GET",
|
||||
status_code=r.status_code,
|
||||
)
|
||||
@@ -64,7 +64,7 @@ class Smargon(object):
|
||||
raise SmargonCommunicationError(
|
||||
f"Smargon GET returned invalid JSON for '{thing}'",
|
||||
endpoint=thing,
|
||||
base_url=self.__base,
|
||||
base_url=self._base,
|
||||
operation="GET",
|
||||
status_code=r.status_code,
|
||||
) from e
|
||||
@@ -72,14 +72,14 @@ class Smargon(object):
|
||||
def gonput(self, thing: str):
|
||||
"""issue a PUT command for some API component on the smargopolo server
|
||||
short hand for goniometer put"""
|
||||
cmd = f"{self.__base}/{thing}"
|
||||
cmd = f"{self._base}/{thing}"
|
||||
try:
|
||||
r = requests.put(cmd, timeout=2.0)
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise SmargonCommunicationError(
|
||||
f"Smargon PUT failed for '{thing}'",
|
||||
endpoint=thing,
|
||||
base_url=self.__base,
|
||||
base_url=self._base,
|
||||
operation="PUT",
|
||||
) from e
|
||||
|
||||
@@ -87,7 +87,7 @@ class Smargon(object):
|
||||
raise SmargonCommunicationError(
|
||||
f"Smargon PUT returned HTTP {r.status_code} for '{thing}': {r.reason}",
|
||||
endpoint=thing,
|
||||
base_url=self.__base,
|
||||
base_url=self._base,
|
||||
operation="PUT",
|
||||
status_code=r.status_code,
|
||||
)
|
||||
@@ -104,7 +104,7 @@ class Smargon(object):
|
||||
|
||||
@mode.setter
|
||||
def mode(self, mode: SmargonMode):
|
||||
if self.__simulated:
|
||||
if self._simulated:
|
||||
return
|
||||
self.gonput(f"mode?mode={mode}")
|
||||
|
||||
@@ -122,21 +122,21 @@ class Smargon(object):
|
||||
return True
|
||||
|
||||
def enable_correction(self):
|
||||
if self.__simulated:
|
||||
if self._simulated:
|
||||
return
|
||||
|
||||
self.gonput("corr_type?corr_type=1")
|
||||
|
||||
def disable_correction(self):
|
||||
if self.__simulated:
|
||||
if self._simulated:
|
||||
return
|
||||
|
||||
self.gonput("corr_type?corr_type=0")
|
||||
|
||||
@property
|
||||
def readback(self) -> SmargonCoordinate:
|
||||
if self.__simulated:
|
||||
return self.__pos
|
||||
if self._simulated:
|
||||
return self._pos
|
||||
|
||||
scs = self.gonget("readbackSCS")
|
||||
return SmargonCoordinate(
|
||||
@@ -147,8 +147,8 @@ class Smargon(object):
|
||||
|
||||
@property
|
||||
def readback_aerotech(self) -> AerotechCoordinate:
|
||||
if self.__simulated:
|
||||
return self.__pos_aero
|
||||
if self._simulated:
|
||||
return self._pos_aero
|
||||
|
||||
acs = self.gonget("readbackAEROTECH")
|
||||
return AerotechCoordinate(
|
||||
@@ -157,8 +157,8 @@ class Smargon(object):
|
||||
|
||||
@property
|
||||
def target(self) -> SmargonCoordinate:
|
||||
if self.__simulated:
|
||||
return self.__pos
|
||||
if self._simulated:
|
||||
return self._pos
|
||||
|
||||
scs = self.gonget("targetSCS") # targetAEROTECH, #targetOMEGA
|
||||
return SmargonCoordinate(
|
||||
@@ -169,8 +169,8 @@ class Smargon(object):
|
||||
|
||||
@target.setter
|
||||
def target(self, coord: SmargonCoordinate):
|
||||
if self.__simulated:
|
||||
self.__pos = coord
|
||||
if self._simulated:
|
||||
self._pos = coord
|
||||
return
|
||||
|
||||
target_string = ""
|
||||
@@ -187,8 +187,8 @@ class Smargon(object):
|
||||
|
||||
@property
|
||||
def target_aerotech(self) -> AerotechCoordinate:
|
||||
if self.__simulated:
|
||||
return self.__pos_aero
|
||||
if self._simulated:
|
||||
return self._pos_aero
|
||||
|
||||
acs = self.gonget("targetAEROTECH") # targetAEROTECH, #targetOMEGA
|
||||
return AerotechCoordinate(
|
||||
@@ -197,8 +197,8 @@ class Smargon(object):
|
||||
|
||||
@target_aerotech.setter
|
||||
def target_aerotech(self, coord: AerotechCoordinate):
|
||||
if self.__simulated:
|
||||
self.__pos_aero = coord
|
||||
if self._simulated:
|
||||
self._pos_aero = coord
|
||||
return
|
||||
|
||||
target_string = ""
|
||||
|
||||
@@ -57,7 +57,7 @@ class TellClient:
|
||||
"""High-level Tell robot API using a pluggable backend"""
|
||||
|
||||
def __init__(self, bl: MXBeamline, backend: TellBackend | None = None):
|
||||
self.__beamline = bl
|
||||
self._beamline = bl
|
||||
self.backend = backend or PShellTellBackend(bl)
|
||||
|
||||
self._aborted = False
|
||||
|
||||
@@ -34,51 +34,51 @@ class ZMQCameraClient:
|
||||
timeout_ms: Receive timeout in milliseconds
|
||||
"""
|
||||
if beamline == MXBeamline.X06DA:
|
||||
self.__simulated = False
|
||||
self.__zmq_url = "tcp://x06da-pserv-01:9089"
|
||||
self._simulated = False
|
||||
self._zmq_url = "tcp://x06da-pserv-01:9089"
|
||||
elif beamline == MXBeamline.X10SA:
|
||||
self.__simulated = False
|
||||
self.__zmq_url = "tcp://x10sa-spark-01:9091"
|
||||
self._simulated = False
|
||||
self._zmq_url = "tcp://x10sa-spark-01:9091"
|
||||
elif beamline == MXBeamline.SIMULATED:
|
||||
self.__simulated = True
|
||||
self.__zmq_url = None
|
||||
self._simulated = True
|
||||
self._zmq_url = None
|
||||
else:
|
||||
raise Exception("unknown beamline")
|
||||
|
||||
self.__timeout_ms = timeout_ms
|
||||
self.__context: Optional[zmq.Context] = None
|
||||
self.__socket: Optional[zmq.Socket] = None
|
||||
self.__last_image: Optional[np.ndarray] = None
|
||||
self.__last_fetch_time: float = 0.0
|
||||
self.__connected = False
|
||||
self._timeout_ms = timeout_ms
|
||||
self._context: Optional[zmq.Context] = None
|
||||
self._socket: Optional[zmq.Socket] = None
|
||||
self._last_image: Optional[np.ndarray] = None
|
||||
self._last_fetch_time: float = 0.0
|
||||
self._connected = False
|
||||
|
||||
def _ensure_connected(self) -> bool:
|
||||
"""Ensure the ZMQ socket is connected. Returns True if successful."""
|
||||
if self.__connected and self.__socket is not None:
|
||||
if self._connected and self._socket is not None:
|
||||
return True
|
||||
|
||||
if self.__simulated or self.__zmq_url is None:
|
||||
if self._simulated or self._zmq_url is None:
|
||||
return False
|
||||
|
||||
try:
|
||||
if self.__context is None:
|
||||
self.__context = zmq.Context()
|
||||
if self._context is None:
|
||||
self._context = zmq.Context()
|
||||
|
||||
if self.__socket is not None:
|
||||
self.__socket.close()
|
||||
if self._socket is not None:
|
||||
self._socket.close()
|
||||
|
||||
self.__socket = self.__context.socket(zmq.SUB)
|
||||
self.__socket.setsockopt(zmq.SUBSCRIBE, b"")
|
||||
self.__socket.setsockopt(zmq.RCVTIMEO, self.__timeout_ms)
|
||||
self._socket = self._context.socket(zmq.SUB)
|
||||
self._socket.setsockopt(zmq.SUBSCRIBE, b"")
|
||||
self._socket.setsockopt(zmq.RCVTIMEO, self._timeout_ms)
|
||||
# Discard old messages, keep only the latest
|
||||
self.__socket.setsockopt(zmq.CONFLATE, 1)
|
||||
self.__socket.connect(self.__zmq_url)
|
||||
self.__connected = True
|
||||
logger.debug(f"ZMQ camera connected to {self.__zmq_url}")
|
||||
self._socket.setsockopt(zmq.CONFLATE, 1)
|
||||
self._socket.connect(self._zmq_url)
|
||||
self._connected = True
|
||||
logger.debug(f"ZMQ camera connected to {self._zmq_url}")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to connect ZMQ camera to {self.__zmq_url}: {e}")
|
||||
self.__connected = False
|
||||
logger.warning(f"Failed to connect ZMQ camera to {self._zmq_url}: {e}")
|
||||
self._connected = False
|
||||
return False
|
||||
|
||||
def get_image(self, gray: bool = False) -> Optional[np.ndarray]:
|
||||
@@ -91,18 +91,18 @@ class ZMQCameraClient:
|
||||
Returns:
|
||||
numpy array with the image, or None if unavailable.
|
||||
"""
|
||||
if self.__simulated:
|
||||
if self._simulated:
|
||||
return None
|
||||
|
||||
if not self._ensure_connected():
|
||||
return None
|
||||
|
||||
try:
|
||||
r = self.__socket.recv_multipart()
|
||||
r = self._socket.recv_multipart()
|
||||
|
||||
if len(r) < 2:
|
||||
logger.debug("ZMQ camera: incomplete message received")
|
||||
return self.__last_image
|
||||
return self._last_image
|
||||
|
||||
data = r[-1]
|
||||
header = None
|
||||
@@ -125,60 +125,60 @@ class ZMQCameraClient:
|
||||
|
||||
if gray:
|
||||
gray_img = cv2.cvtColor(rgb, cv2.COLOR_RGB2GRAY)
|
||||
self.__last_image = gray_img
|
||||
self._last_image = gray_img
|
||||
return gray_img
|
||||
else:
|
||||
self.__last_image = rgb
|
||||
self._last_image = rgb
|
||||
return rgb
|
||||
else:
|
||||
logger.debug("ZMQ camera: unexpected image format")
|
||||
return self.__last_image
|
||||
return self._last_image
|
||||
|
||||
except zmq.Again:
|
||||
logger.debug("ZMQ camera: timeout waiting for frame")
|
||||
return self.__last_image
|
||||
return self._last_image
|
||||
except Exception as e:
|
||||
logger.warning(f"ZMQ camera error: {e}")
|
||||
self.__connected = False
|
||||
return self.__last_image
|
||||
self._connected = False
|
||||
return self._last_image
|
||||
|
||||
def is_available(self) -> bool:
|
||||
"""Check if ZMQ camera is available and responding."""
|
||||
if self.__simulated:
|
||||
if self._simulated:
|
||||
return False
|
||||
|
||||
if not self._ensure_connected():
|
||||
return False
|
||||
|
||||
try:
|
||||
old_timeout = self.__socket.getsockopt(zmq.RCVTIMEO)
|
||||
self.__socket.setsockopt(zmq.RCVTIMEO, 500)
|
||||
old_timeout = self._socket.getsockopt(zmq.RCVTIMEO)
|
||||
self._socket.setsockopt(zmq.RCVTIMEO, 500)
|
||||
try:
|
||||
self.__socket.recv_multipart()
|
||||
self.__socket.setsockopt(zmq.RCVTIMEO, old_timeout)
|
||||
self._socket.recv_multipart()
|
||||
self._socket.setsockopt(zmq.RCVTIMEO, old_timeout)
|
||||
return True
|
||||
except zmq.Again:
|
||||
self.__socket.setsockopt(zmq.RCVTIMEO, old_timeout)
|
||||
self._socket.setsockopt(zmq.RCVTIMEO, old_timeout)
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def close(self):
|
||||
"""Close the ZMQ connection."""
|
||||
if self.__socket:
|
||||
if self._socket:
|
||||
try:
|
||||
self.__socket.close()
|
||||
self._socket.close()
|
||||
except Exception:
|
||||
pass
|
||||
self.__socket = None
|
||||
if self.__context:
|
||||
self._socket = None
|
||||
if self._context:
|
||||
try:
|
||||
self.__context.term()
|
||||
self._context.term()
|
||||
except Exception:
|
||||
pass
|
||||
self.__context = None
|
||||
self.__connected = False
|
||||
self._context = None
|
||||
self._connected = False
|
||||
|
||||
@property
|
||||
def url(self) -> str:
|
||||
return self.__zmq_url if self.__zmq_url else "simulated"
|
||||
return self._zmq_url if self._zmq_url else "simulated"
|
||||
|
||||
+34
-34
@@ -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
|
||||
@@ -1413,7 +1413,7 @@ class MainWindow(QMainWindow):
|
||||
self.tell_samples_dock.visibilityChanged.connect(show_samples_action.setChecked)
|
||||
view_menu.addAction(show_samples_action)
|
||||
|
||||
if self.__decoded_token.staff:
|
||||
if self._decoded_token.staff:
|
||||
show_reference_tools_action = QAction("Show Reference Tools", self)
|
||||
show_reference_tools_action.setCheckable(True)
|
||||
show_reference_tools_action.setChecked(True)
|
||||
@@ -1549,7 +1549,7 @@ class MainWindow(QMainWindow):
|
||||
refresh_axis_cameras_action.triggered.connect(self.refresh_axis_cameras)
|
||||
help_menu.addAction(refresh_axis_cameras_action)
|
||||
|
||||
if self.__decoded_token.staff:
|
||||
if self._decoded_token.staff:
|
||||
local_contact_action = QAction("Local Contact", self)
|
||||
local_contact_action.triggered.connect(self.show_local_contact)
|
||||
help_menu.addAction(local_contact_action)
|
||||
@@ -1596,7 +1596,7 @@ class MainWindow(QMainWindow):
|
||||
self.prediction_metrics_dock.setVisible(False)
|
||||
self.log_dock.setVisible(False)
|
||||
|
||||
if self.__decoded_token.staff:
|
||||
if self._decoded_token.staff:
|
||||
self.ref_tools_dock.setVisible(True)
|
||||
self.ref_tools_dock.raise_()
|
||||
else:
|
||||
@@ -1619,7 +1619,7 @@ class MainWindow(QMainWindow):
|
||||
if self._dev_help_dialog is None:
|
||||
self._dev_help_dialog = DeveloperHelpDialog(
|
||||
daq=self.daq,
|
||||
is_staff=bool(getattr(self.__decoded_token, "staff", False)),
|
||||
is_staff=bool(getattr(self._decoded_token, "staff", False)),
|
||||
parent=self,
|
||||
)
|
||||
self._dev_help_dialog.refresh()
|
||||
@@ -1912,7 +1912,7 @@ class MainWindow(QMainWindow):
|
||||
|
||||
# 4. Surface recovery UI
|
||||
try:
|
||||
if bool(getattr(self.__decoded_token, "staff", False)):
|
||||
if bool(getattr(self._decoded_token, "staff", False)):
|
||||
if is_detector_failure:
|
||||
QMessageBox.critical(
|
||||
self,
|
||||
@@ -1958,7 +1958,7 @@ class MainWindow(QMainWindow):
|
||||
logger.error(f"Failed to surface recovery UI after critical failure: {e}")
|
||||
|
||||
def show_beamline_recovery(self) -> None:
|
||||
if not bool(getattr(self.__decoded_token, "staff", False)):
|
||||
if not bool(getattr(self._decoded_token, "staff", False)):
|
||||
return
|
||||
if self._beamline_recovery_dialog is None:
|
||||
self._beamline_recovery_dialog = BeamlineRecoveryDialog(daq=self.daq, parent=self)
|
||||
@@ -1967,7 +1967,7 @@ class MainWindow(QMainWindow):
|
||||
self._beamline_recovery_dialog.activateWindow()
|
||||
|
||||
def show_local_contact(self, tab_name: str = "Status") -> None:
|
||||
if not bool(getattr(self.__decoded_token, "staff", False)):
|
||||
if not bool(getattr(self._decoded_token, "staff", False)):
|
||||
return
|
||||
if self._local_contact_dialog is None:
|
||||
self._local_contact_dialog = LocalContactDialog(daq=self.daq, parent=self)
|
||||
@@ -2024,7 +2024,7 @@ class MainWindow(QMainWindow):
|
||||
|
||||
self._refresh_compact_queue_preview()
|
||||
|
||||
current_session = int(getattr(self.__decoded_token, "session", -1))
|
||||
current_session = int(getattr(self._decoded_token, "session", -1))
|
||||
for gui in getattr(s, "open_guis", []) or []:
|
||||
try:
|
||||
if int(gui.session) == current_session and bool(gui.close_requested):
|
||||
@@ -2040,11 +2040,11 @@ class MainWindow(QMainWindow):
|
||||
if self._remote_close_deadline_ts is not None:
|
||||
self._clear_remote_close_request()
|
||||
|
||||
if not self.__mounting and s.state == BeamlineStateEnum.RobotSampleExchange:
|
||||
self.__mounting = True
|
||||
if not self._mounting and s.state == BeamlineStateEnum.RobotSampleExchange:
|
||||
self._mounting = True
|
||||
self.video_tab.setCurrentWidget(self.beamline_combined_panel)
|
||||
elif self.__mounting and s.state != BeamlineStateEnum.RobotSampleExchange:
|
||||
self.__mounting = False
|
||||
elif self._mounting and s.state != BeamlineStateEnum.RobotSampleExchange:
|
||||
self._mounting = False
|
||||
self.video_tab.setCurrentWidget(self.sample_camera)
|
||||
|
||||
# ========== BATON DIALOG HANDLING ==========
|
||||
@@ -2403,7 +2403,7 @@ class MainWindow(QMainWindow):
|
||||
|
||||
self._last_interaction_report_ts = now
|
||||
try:
|
||||
self.daq.report_gui_interaction(int(self.__decoded_token.session))
|
||||
self.daq.report_gui_interaction(int(self._decoded_token.session))
|
||||
except Exception as e:
|
||||
logger.debug(f"Failed to report GUI interaction: {e}")
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ class SampleQueueSpreadsheet(QAbstractTableModel):
|
||||
super().__init__(parent)
|
||||
if samples is None:
|
||||
samples = []
|
||||
self.__running = False
|
||||
self._running = False
|
||||
self._show_user = bool(show_user)
|
||||
self.samples: list[SampleShortInfo] = samples
|
||||
self.header = (
|
||||
@@ -54,7 +54,7 @@ class SampleQueueSpreadsheet(QAbstractTableModel):
|
||||
return Qt.AlignmentFlag.AlignCenter
|
||||
elif role == Qt.ItemDataRole.BackgroundRole:
|
||||
if index.row() == 0:
|
||||
if self.__running:
|
||||
if self._running:
|
||||
return QBrush(QColor(255, 102, 0))
|
||||
else:
|
||||
return QBrush(QColor(114, 159, 207))
|
||||
@@ -113,7 +113,7 @@ class SampleQueueSpreadsheet(QAbstractTableModel):
|
||||
return True
|
||||
|
||||
def set_running(self, running: bool):
|
||||
self.__running = running
|
||||
self._running = running
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def remove_sample(self, db_id: int):
|
||||
|
||||
@@ -57,12 +57,12 @@ class UserSampleSpreadsheet(QAbstractTableModel):
|
||||
]
|
||||
self.current_sample = current_sample
|
||||
self.current_puck = current_puck
|
||||
self.__sort_col = 3
|
||||
self.__sort_order = Qt.SortOrder.AscendingOrder
|
||||
self._sort_col = 3
|
||||
self._sort_order = Qt.SortOrder.AscendingOrder
|
||||
|
||||
self.__filters: dict[int, str] = {}
|
||||
self.__filter_col: int | None = 5
|
||||
self.__filter_value: str | None = None
|
||||
self._filters: dict[int, str] = {}
|
||||
self._filter_col: int | None = 5
|
||||
self._filter_value: str | None = None
|
||||
self.current_pgroup: str | None = None
|
||||
self.show_all_pgroups: bool = False
|
||||
|
||||
@@ -77,20 +77,20 @@ class UserSampleSpreadsheet(QAbstractTableModel):
|
||||
self.endResetModel()
|
||||
|
||||
def rowCount(self, parent=None):
|
||||
return len(self.__sorted_samples)
|
||||
return len(self._sorted_samples)
|
||||
|
||||
def columnCount(self, parent=None):
|
||||
return len(self.header)
|
||||
|
||||
def data(self, index, role=None):
|
||||
if role == Qt.ItemDataRole.DisplayRole:
|
||||
return get_entry(self.__sorted_samples[index.row()], index.column())
|
||||
return get_entry(self._sorted_samples[index.row()], index.column())
|
||||
elif role == Qt.ItemDataRole.TextAlignmentRole: # Align text to center
|
||||
return Qt.AlignmentFlag.AlignCenter
|
||||
elif role == Qt.ItemDataRole.BackgroundRole:
|
||||
if self.__sorted_samples[index.row()].db_id == self.current_sample:
|
||||
if self._sorted_samples[index.row()].db_id == self.current_sample:
|
||||
return QBrush(QColor(114, 159, 207)) # darker blue
|
||||
if self.__sorted_samples[index.row()].puck_name == self.current_puck:
|
||||
if self._sorted_samples[index.row()].puck_name == self.current_puck:
|
||||
return QBrush(QColor(216, 228, 253)) # light blue
|
||||
return QBrush(QColor(255, 255, 255)) # White
|
||||
return None # For other roles, return None
|
||||
@@ -118,39 +118,39 @@ class UserSampleSpreadsheet(QAbstractTableModel):
|
||||
|
||||
def sort(self, column, order):
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
self.__sort_order = order
|
||||
self.__sort_col = column
|
||||
self._sort_order = order
|
||||
self._sort_col = column
|
||||
self._sort()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def _sort(self):
|
||||
filtered = self._apply_filter(self.samples)
|
||||
if self.__sort_col == 3:
|
||||
self.__sorted_samples = sorted(
|
||||
if self._sort_col == 3:
|
||||
self._sorted_samples = sorted(
|
||||
filtered,
|
||||
key=lambda row: row.loc_str_sort(),
|
||||
reverse=(
|
||||
self.__sort_order == Qt.SortOrder.DescendingOrder
|
||||
self._sort_order == Qt.SortOrder.DescendingOrder
|
||||
), # Reverse for descending order
|
||||
)
|
||||
else:
|
||||
# Sort the samples based on the specified column and order
|
||||
self.__sorted_samples = sorted(
|
||||
self._sorted_samples = sorted(
|
||||
filtered,
|
||||
key=lambda row: get_entry(row, self.__sort_col),
|
||||
key=lambda row: get_entry(row, self._sort_col),
|
||||
reverse=(
|
||||
self.__sort_order == Qt.SortOrder.DescendingOrder
|
||||
self._sort_order == Qt.SortOrder.DescendingOrder
|
||||
), # Reverse for descending order
|
||||
)
|
||||
|
||||
def _apply_filter(self, rows: list[SampleShortInfo]) -> list[SampleShortInfo]:
|
||||
# Default filter by User using current p-group if no explicit filter set
|
||||
filters: dict[int, str] = {
|
||||
col: v for col, v in (self.__filters or {}).items() if (v or "").strip()
|
||||
col: v for col, v in (self._filters or {}).items() if (v or "").strip()
|
||||
}
|
||||
|
||||
if self.__filter_col is not None and (self.__filter_value or "").strip():
|
||||
filters[self.__filter_col] = self.__filter_value
|
||||
if self._filter_col is not None and (self._filter_value or "").strip():
|
||||
filters[self._filter_col] = self._filter_value
|
||||
|
||||
if 5 not in filters and self.current_pgroup and not self.show_all_pgroups:
|
||||
filters[5] = self.current_pgroup
|
||||
@@ -183,13 +183,13 @@ class UserSampleSpreadsheet(QAbstractTableModel):
|
||||
sample_data = SampleShortInfoList(s=[])
|
||||
|
||||
for i in sorted(set(index.row() for index in indexes)):
|
||||
sample_data.s.append(self.__sorted_samples[i])
|
||||
sample_data.s.append(self._sorted_samples[i])
|
||||
|
||||
mime_data.setText(sample_data.model_dump_json())
|
||||
return mime_data
|
||||
|
||||
def get_id(self, row: int) -> SampleShortInfo:
|
||||
return self.__sorted_samples[row]
|
||||
return self._sorted_samples[row]
|
||||
|
||||
def set_filter(self, field: str, text: str | None):
|
||||
try:
|
||||
@@ -197,22 +197,22 @@ class UserSampleSpreadsheet(QAbstractTableModel):
|
||||
except ValueError:
|
||||
col = None
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
self.__filter_col = col if text and text.strip() and col is not None else None
|
||||
self.__filter_value = text.strip() if text else None
|
||||
self._filter_col = col if text and text.strip() and col is not None else None
|
||||
self._filter_value = text.strip() if text else None
|
||||
# mirror into multi-filter map
|
||||
if col is not None:
|
||||
if text and text.strip():
|
||||
self.__filters[col] = text.strip()
|
||||
self._filters[col] = text.strip()
|
||||
else:
|
||||
self.__filters.pop(col, None)
|
||||
self._filters.pop(col, None)
|
||||
self._sort()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def clear_filter(self):
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
self.__filter_col = None
|
||||
self.__filter_value = None
|
||||
self.__filters.clear()
|
||||
self._filter_col = None
|
||||
self._filter_value = None
|
||||
self._filters.clear()
|
||||
self._sort()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
@@ -225,9 +225,9 @@ class UserSampleSpreadsheet(QAbstractTableModel):
|
||||
def set_column_filter(self, column: int, text: str | None):
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
if text and text.strip():
|
||||
self.__filters[column] = text.strip()
|
||||
self._filters[column] = text.strip()
|
||||
else:
|
||||
self.__filters.pop(column, None)
|
||||
self._filters.pop(column, None)
|
||||
self._sort()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
@@ -243,11 +243,11 @@ class UserSampleSpreadsheet(QAbstractTableModel):
|
||||
def unique_values_for_column(self, column: int, limit: int = 200) -> list[str]:
|
||||
"""Get unique values for a column from currently filtered samples (excluding this column's filter)."""
|
||||
# Get currently filtered samples, but exclude the filter for this column
|
||||
temp_filter = self.__filters.pop(column, None)
|
||||
temp_filter = self._filters.pop(column, None)
|
||||
filtered_samples = self._apply_filter(self.samples)
|
||||
# Restore the filter
|
||||
if temp_filter is not None:
|
||||
self.__filters[column] = temp_filter
|
||||
self._filters[column] = temp_filter
|
||||
|
||||
seen: set[str] = set()
|
||||
out: list[str] = []
|
||||
@@ -283,10 +283,10 @@ class UserSampleSpreadsheet(QAbstractTableModel):
|
||||
def suggested_prefixes_for_sample_name(self, limit: int = 200) -> list[str]:
|
||||
"""Get sample name prefixes from currently filtered samples (excluding column 0 filter)."""
|
||||
# Get currently filtered samples, excluding the sample name filter
|
||||
temp_filter = self.__filters.pop(0, None)
|
||||
temp_filter = self._filters.pop(0, None)
|
||||
filtered_samples = self._apply_filter(self.samples)
|
||||
if temp_filter is not None:
|
||||
self.__filters[0] = temp_filter
|
||||
self._filters[0] = temp_filter
|
||||
|
||||
rx = re.compile(r"^([A-Za-z]+)")
|
||||
counts: dict[str, int] = {}
|
||||
@@ -307,10 +307,10 @@ class UserSampleSpreadsheet(QAbstractTableModel):
|
||||
def suggested_prefixes_for_location(self, limit: int = 200) -> tuple[list[str], list[str]]:
|
||||
"""Get location prefixes from currently filtered samples (excluding column 3 filter)."""
|
||||
# Get currently filtered samples, excluding the location filter
|
||||
temp_filter = self.__filters.pop(3, None)
|
||||
temp_filter = self._filters.pop(3, None)
|
||||
filtered_samples = self._apply_filter(self.samples)
|
||||
if temp_filter is not None:
|
||||
self.__filters[3] = temp_filter
|
||||
self._filters[3] = temp_filter
|
||||
|
||||
seg_seen: set[str] = set()
|
||||
segpos_seen: set[str] = set()
|
||||
|
||||
@@ -17,7 +17,7 @@ class AbrTweakButtons(QWidget):
|
||||
def __init__(self, step_mm, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.__step_mm = step_mm
|
||||
self._step_mm = step_mm
|
||||
grid_layout = QGridLayout(self)
|
||||
grid_layout.setColumnStretch(0, 1)
|
||||
grid_layout.setColumnStretch(1, 0)
|
||||
@@ -70,16 +70,16 @@ class AbrTweakButtons(QWidget):
|
||||
self.abr_tweak.emit(
|
||||
AerotechCoordinate(
|
||||
at_mm=Coordinate(
|
||||
x=self.__step_mm * payload["x"],
|
||||
y=self.__step_mm * payload["y"],
|
||||
z=self.__step_mm * payload["z"],
|
||||
x=self._step_mm * payload["x"],
|
||||
y=self._step_mm * payload["y"],
|
||||
z=self._step_mm * payload["z"],
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@Slot(float)
|
||||
def set_step(self, val_um: float):
|
||||
self.__step_mm = val_um / 1000.0
|
||||
self._step_mm = val_um / 1000.0
|
||||
|
||||
|
||||
class AbrTweakWidget(QWidget):
|
||||
@@ -97,15 +97,15 @@ class AbrTweakWidget(QWidget):
|
||||
|
||||
grid_layout.addWidget(TitleLabel("ABR meas. pos.", self), 0, 0, 1, 3)
|
||||
|
||||
self.__abr_buttons = AbrTweakButtons(DEFAULT_ABR_STEP_UM / 1000, parent=self)
|
||||
grid_layout.addWidget(self.__abr_buttons, 1, 0, 1, 4)
|
||||
self.__abr_buttons.abr_tweak.connect(self.abr_button_pressed)
|
||||
self._abr_buttons = AbrTweakButtons(DEFAULT_ABR_STEP_UM / 1000, parent=self)
|
||||
grid_layout.addWidget(self._abr_buttons, 1, 0, 1, 4)
|
||||
self._abr_buttons.abr_tweak.connect(self.abr_button_pressed)
|
||||
|
||||
grid_layout.addWidget(QLabel("Step", parent=self), 2, 0)
|
||||
self.__step_um = NumberLineEdit(1, 1000, DEFAULT_ABR_STEP_UM, 0, parent=self)
|
||||
grid_layout.addWidget(self.__step_um, 2, 1)
|
||||
self._step_um = NumberLineEdit(1, 1000, DEFAULT_ABR_STEP_UM, 0, parent=self)
|
||||
grid_layout.addWidget(self._step_um, 2, 1)
|
||||
grid_layout.addWidget(QLabel("μm", parent=self), 2, 2)
|
||||
self.__step_um.newValue.connect(self.__abr_buttons.set_step)
|
||||
self._step_um.newValue.connect(self._abr_buttons.set_step)
|
||||
|
||||
save_button = QPushButton("Save ABR pos.")
|
||||
grid_layout.addWidget(save_button, 3, 0, 1, 3)
|
||||
@@ -129,19 +129,19 @@ class AbrTweakWidget(QWidget):
|
||||
|
||||
@Slot(DAQStatusModel)
|
||||
def update_daq_status(self, s: DAQStatusModel):
|
||||
self.__abr_buttons.gmx_label.setText(f"{s.geom.aerotech_meas.x:.3f}")
|
||||
self._abr_buttons.gmx_label.setText(f"{s.geom.aerotech_meas.x:.3f}")
|
||||
if abs(s.geom.aerotech.x) >= 0.001:
|
||||
self.__abr_buttons.gmx_label.setStyleSheet("color: rgb(255, 0, 0);")
|
||||
self._abr_buttons.gmx_label.setStyleSheet("color: rgb(255, 0, 0);")
|
||||
else:
|
||||
self.__abr_buttons.gmx_label.setStyleSheet("color: rgb(0, 0, 0);")
|
||||
self.__abr_buttons.gmy_label.setText(f"{s.geom.aerotech_meas.y:.3f}")
|
||||
self._abr_buttons.gmx_label.setStyleSheet("color: rgb(0, 0, 0);")
|
||||
self._abr_buttons.gmy_label.setText(f"{s.geom.aerotech_meas.y:.3f}")
|
||||
if abs(s.geom.aerotech.y) >= 0.001:
|
||||
self.__abr_buttons.gmy_label.setStyleSheet("color: rgb(255, 0, 0);")
|
||||
self._abr_buttons.gmy_label.setStyleSheet("color: rgb(255, 0, 0);")
|
||||
else:
|
||||
self.__abr_buttons.gmy_label.setStyleSheet("color: rgb(0, 0, 0);")
|
||||
self._abr_buttons.gmy_label.setStyleSheet("color: rgb(0, 0, 0);")
|
||||
|
||||
self.__abr_buttons.gmz_label.setText(f"{s.geom.aerotech_meas.z:.3f}")
|
||||
self._abr_buttons.gmz_label.setText(f"{s.geom.aerotech_meas.z:.3f}")
|
||||
if abs(s.geom.aerotech.z) >= 0.001:
|
||||
self.__abr_buttons.gmz_label.setStyleSheet("color: rgb(255, 0, 0);")
|
||||
self._abr_buttons.gmz_label.setStyleSheet("color: rgb(255, 0, 0);")
|
||||
else:
|
||||
self.__abr_buttons.gmz_label.setStyleSheet("color: rgb(0, 0, 0);")
|
||||
self._abr_buttons.gmz_label.setStyleSheet("color: rgb(0, 0, 0);")
|
||||
|
||||
@@ -33,23 +33,23 @@ class DataCollectionSettings(QFrame):
|
||||
self.file_path_panel = FilePathPanel(self)
|
||||
v_layout.addWidget(self.file_path_panel)
|
||||
|
||||
self.__tab_widget = QTabWidget()
|
||||
self._tab_widget = QTabWidget()
|
||||
|
||||
self.raster = RasterDataCollectionPanel(
|
||||
parent=self, raster_mgr=raster_mgr, diffraction=diffraction
|
||||
)
|
||||
self.__tab_widget.addTab(self.raster, "Raster scan")
|
||||
self._tab_widget.addTab(self.raster, "Raster scan")
|
||||
|
||||
self.screening = RotationDataCollectionPanel(parent=self, diffraction=diffraction)
|
||||
self.__tab_widget.addTab(self.screening, "Rotation")
|
||||
self._tab_widget.addTab(self.screening, "Rotation")
|
||||
|
||||
self.simple = SimpleRotationSettingsPanel(parent=self)
|
||||
self.__tab_widget.addTab(self.simple, "Simple")
|
||||
self._tab_widget.addTab(self.simple, "Simple")
|
||||
|
||||
self.fluo = FluorescenceDataCollectionPanel(parent=self)
|
||||
self.__tab_widget.addTab(self.fluo, "XRF")
|
||||
self._tab_widget.addTab(self.fluo, "XRF")
|
||||
|
||||
v_layout.addWidget(self.__tab_widget)
|
||||
v_layout.addWidget(self._tab_widget)
|
||||
v_layout.addStretch()
|
||||
|
||||
abort_button = QPushButton("Abort measurement", parent=self)
|
||||
@@ -65,13 +65,13 @@ class DataCollectionSettings(QFrame):
|
||||
self.file_path_panel.path_updated.connect(raster_mgr.update_filename)
|
||||
self.file_path_panel.path_updated.connect(self.screening.update_filename)
|
||||
self.file_path_panel.path_updated.connect(self.simple.update_filename)
|
||||
self.__sample_id = None
|
||||
self._sample_id = None
|
||||
|
||||
self.__tab_widget.currentChanged.connect(self._on_tab_changed)
|
||||
self._tab_widget.currentChanged.connect(self._on_tab_changed)
|
||||
|
||||
@Slot()
|
||||
def switch_to_raster(self):
|
||||
self.__tab_widget.setCurrentIndex(0)
|
||||
self._tab_widget.setCurrentIndex(0)
|
||||
|
||||
@Slot()
|
||||
def cancel_button_clicked(self):
|
||||
@@ -82,9 +82,9 @@ class DataCollectionSettings(QFrame):
|
||||
self.raster.update_daq_status(s)
|
||||
self.screening.update_daq_status(s)
|
||||
self.simple.update_daq_status(s)
|
||||
if s.sample is not None and s.sample.db_id != self.__sample_id:
|
||||
self.__sample_id = s.sample.db_id
|
||||
self.__tab_widget.setCurrentIndex(0)
|
||||
if s.sample is not None and s.sample.db_id != self._sample_id:
|
||||
self._sample_id = s.sample.db_id
|
||||
self._tab_widget.setCurrentIndex(0)
|
||||
|
||||
@Slot(int)
|
||||
def _on_tab_changed(self, idx: int):
|
||||
|
||||
@@ -23,18 +23,18 @@ class FilePathPanel(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
grid_layout = QGridLayout(self)
|
||||
self.__sample_name = "sample"
|
||||
self.__sample_id = -1
|
||||
self.__dewar_pos = "None"
|
||||
self.__puck_name = "Manual"
|
||||
self.__puck_pos = 0
|
||||
self.__curr_pgroup = "p11206"
|
||||
self.__beamline = mx_beamline().value.lower()
|
||||
self._sample_name = "sample"
|
||||
self._sample_id = -1
|
||||
self._dewar_pos = "None"
|
||||
self._puck_name = "Manual"
|
||||
self._puck_pos = 0
|
||||
self._curr_pgroup = "p11206"
|
||||
self._beamline = mx_beamline().value.lower()
|
||||
|
||||
self.__filename = ""
|
||||
self.__scan_kind = "raster" # default: "rotation" | "screening" | "raster"
|
||||
self._filename = ""
|
||||
self._scan_kind = "raster" # default: "rotation" | "screening" | "raster"
|
||||
|
||||
self.__formatted_date = datetime.now().strftime("%Y%m%d")
|
||||
self._formatted_date = datetime.now().strftime("%Y%m%d")
|
||||
|
||||
grid_layout.addWidget(TitleLabel("Dataset path", self), 0, 0, 1, 2)
|
||||
|
||||
@@ -86,22 +86,22 @@ class FilePathPanel(QWidget):
|
||||
|
||||
def _expand_macros(self, base: str, rn: int) -> str:
|
||||
name = f"{base}_{rn:03d}"
|
||||
name = name.replace("{date}", self.__formatted_date)
|
||||
name = name.replace("{sample}", self.__sample_name)
|
||||
name = name.replace("{CrystalName}", self.__sample_name)
|
||||
name = name.replace("{puck}", self.__puck_name)
|
||||
name = name.replace("{position}", f"{self.__puck_pos:02d}")
|
||||
name = name.replace("{sample_id}", f"{self.__sample_id}")
|
||||
name = name.replace("{beamline}", f"{self.__beamline}")
|
||||
name = name.replace("{date}", self._formatted_date)
|
||||
name = name.replace("{sample}", self._sample_name)
|
||||
name = name.replace("{CrystalName}", self._sample_name)
|
||||
name = name.replace("{puck}", self._puck_name)
|
||||
name = name.replace("{position}", f"{self._puck_pos:02d}")
|
||||
name = name.replace("{sample_id}", f"{self._sample_id}")
|
||||
name = name.replace("{beamline}", f"{self._beamline}")
|
||||
name = name.replace(
|
||||
"{prefix}", f"{self.__puck_name}/{self.__puck_pos:02d}/{self.__sample_name}"
|
||||
"{prefix}", f"{self._puck_name}/{self._puck_pos:02d}/{self._sample_name}"
|
||||
)
|
||||
return name
|
||||
|
||||
def _effective_dataset_base(self, base_no_run: str) -> str:
|
||||
root = Path("/sls/mx/data") / self.__curr_pgroup / "raw"
|
||||
root = Path("/sls/mx/data") / self._curr_pgroup / "raw"
|
||||
p = Path(base_no_run)
|
||||
match self.__scan_kind:
|
||||
match self._scan_kind:
|
||||
case "rotation":
|
||||
p = Path("data") / p
|
||||
case "screening":
|
||||
@@ -150,16 +150,16 @@ class FilePathPanel(QWidget):
|
||||
self.run_number_edit.blockSignals(False)
|
||||
|
||||
# Store the GUI’s base (without applying scan-kind transforms) for wiring into requests later
|
||||
self.__filename = self._expand_macros(base, rn)
|
||||
self._filename = self._expand_macros(base, rn)
|
||||
|
||||
# Preview label shows the effective path (what will be written)
|
||||
effective = self._effective_dataset_base(self.__filename)
|
||||
effective = self._effective_dataset_base(self._filename)
|
||||
exists = os.path.exists(f"{effective}_master.h5") or os.path.exists(effective)
|
||||
self.file_name_label.setText(effective + "_master.h5")
|
||||
self.file_name_label.setStyleSheet(
|
||||
"color: rgb(200, 0, 0);" if exists else "color: rgb(0, 0, 0);"
|
||||
)
|
||||
self.path_updated.emit(self.__filename)
|
||||
self.path_updated.emit(self._filename)
|
||||
|
||||
@Slot()
|
||||
def increment_run_number(self):
|
||||
@@ -184,44 +184,44 @@ class FilePathPanel(QWidget):
|
||||
|
||||
@property
|
||||
def filename(self) -> str:
|
||||
return self.__filename
|
||||
return self._filename
|
||||
|
||||
@Slot(SampleShortInfo)
|
||||
def update_sample(self, sample: SampleShortInfo):
|
||||
if sample is None:
|
||||
self.__sample_id = -1
|
||||
self.__sample_name = "sample"
|
||||
self.__dewar_pos = "None"
|
||||
self.__puck_name = "Manual"
|
||||
self.__puck_pos = 99
|
||||
self.directory_edit.setText(f"{self.__formatted_date}/test")
|
||||
self._sample_id = -1
|
||||
self._sample_name = "sample"
|
||||
self._dewar_pos = "None"
|
||||
self._puck_name = "Manual"
|
||||
self._puck_pos = 99
|
||||
self.directory_edit.setText(f"{self._formatted_date}/test")
|
||||
else:
|
||||
self.__sample_id = sample.db_id
|
||||
self.__sample_name = sample.sample_name
|
||||
self.__dewar_pos = sample.loc_str()
|
||||
self.__puck_name = sample.puck_name
|
||||
self.__puck_pos = sample.pin
|
||||
self._sample_id = sample.db_id
|
||||
self._sample_name = sample.sample_name
|
||||
self._dewar_pos = sample.loc_str()
|
||||
self._puck_name = sample.puck_name
|
||||
self._puck_pos = sample.pin
|
||||
self.run_number_edit.setValue(1)
|
||||
|
||||
if sample.aaredb_params is not None and sample.aaredb_params.directory is not None:
|
||||
self.directory_edit.setText(f"{sample.aaredb_params.directory}")
|
||||
elif sample.location is not None:
|
||||
self.directory_edit.setText(
|
||||
f"{self.__formatted_date}/{self.__puck_name}/{self.__puck_pos:02d}"
|
||||
f"{self._formatted_date}/{self._puck_name}/{self._puck_pos:02d}"
|
||||
)
|
||||
else:
|
||||
self.directory_edit.setText(f"{self.__formatted_date}/manual/{self.__sample_name}")
|
||||
self.directory_edit.setText(f"{self._formatted_date}/manual/{self._sample_name}")
|
||||
self.update_filename()
|
||||
|
||||
@Slot(DAQStatusModel)
|
||||
def update_daq_status(self, status: DAQStatusModel):
|
||||
if status.session.current_pgroup:
|
||||
self.__curr_pgroup = status.session.current_pgroup
|
||||
self._curr_pgroup = status.session.current_pgroup
|
||||
|
||||
@Slot(str)
|
||||
def set_scan_kind(self, kind: str):
|
||||
# kind in {"rotation","screening","raster"}
|
||||
self.__scan_kind = kind
|
||||
self._scan_kind = kind
|
||||
self.update_filename()
|
||||
|
||||
def effective_path_for_base(self, base_with_run: str) -> str:
|
||||
|
||||
@@ -13,16 +13,16 @@ class ManualSamplePanel(QWidget):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.__sample_name = "Lyso"
|
||||
self._sample_name = "Lyso"
|
||||
|
||||
self.__pgroup = "p16371"
|
||||
self._pgroup = "p16371"
|
||||
|
||||
grid_layout = QGridLayout(self)
|
||||
|
||||
grid_layout.addWidget(TitleLabel("Manual sample", self), 0, 0, 1, 3)
|
||||
|
||||
grid_layout.addWidget(QLabel("Sample"), 1, 0)
|
||||
self._text_name = QLineEdit(self.__sample_name)
|
||||
self._text_name = QLineEdit(self._sample_name)
|
||||
grid_layout.addWidget(self._text_name, 1, 1)
|
||||
self._text_name.textChanged.connect(self._name_changed)
|
||||
self._text_name.setStyleSheet("background-color: rgb(255, 255, 255);")
|
||||
@@ -95,7 +95,7 @@ class ManualSamplePanel(QWidget):
|
||||
|
||||
@Slot(str)
|
||||
def _name_changed(self, s: str):
|
||||
self.__sample_name = s
|
||||
self._sample_name = s
|
||||
|
||||
@Slot()
|
||||
def _add_clicked(self):
|
||||
@@ -111,14 +111,14 @@ class ManualSamplePanel(QWidget):
|
||||
db_id=-1,
|
||||
puck_name="",
|
||||
dewar_name="",
|
||||
sample_name=self.__sample_name,
|
||||
sample_name=self._sample_name,
|
||||
run_number=1, # can't be -1. So have defaulted to 1 for now.
|
||||
pin=1,
|
||||
aaredb_params=data_processing,
|
||||
user=self.__pgroup,
|
||||
user=self._pgroup,
|
||||
)
|
||||
self.sample_manual.emit(s)
|
||||
|
||||
@Slot(DAQStatusModel)
|
||||
def update_daq_status(self, s: DAQStatusModel):
|
||||
self.__pgroup = s.session.current_pgroup
|
||||
self._pgroup = s.session.current_pgroup
|
||||
|
||||
@@ -25,7 +25,7 @@ class OmegaPanel(QWidget):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.__omega = 0
|
||||
self._omega = 0
|
||||
|
||||
grid_layout = QGridLayout(self)
|
||||
|
||||
@@ -69,4 +69,4 @@ class OmegaPanel(QWidget):
|
||||
@Slot(DAQStatusModel)
|
||||
def update_daq_status(self, s: DAQStatusModel):
|
||||
self.edit.edit.update_value(s.geom.omega_deg)
|
||||
self.__omega = s.geom.omega_deg
|
||||
self._omega = s.geom.omega_deg
|
||||
|
||||
@@ -42,17 +42,15 @@ class RasterDataCollectionPanel(ScanSettingsPanel):
|
||||
|
||||
self._previous_sample_was_none_raster = True
|
||||
|
||||
self.__n_x = raster_mgr.active_grid.n_x
|
||||
self.__n_y = raster_mgr.active_grid.n_y
|
||||
self.__size_x = raster_mgr.active_grid.grid_size_mm.x * 1000.0
|
||||
self.__size_y = raster_mgr.active_grid.grid_size_mm.y * 1000.0
|
||||
self.__total_time = raster_mgr.active_grid.exp_time_s * self.__n_x * self.__n_y
|
||||
self._n_x = raster_mgr.active_grid.n_x
|
||||
self._n_y = raster_mgr.active_grid.n_y
|
||||
self._size_x = raster_mgr.active_grid.grid_size_mm.x * 1000.0
|
||||
self._size_y = raster_mgr.active_grid.grid_size_mm.y * 1000.0
|
||||
self._total_time = raster_mgr.active_grid.exp_time_s * self._n_x * self._n_y
|
||||
|
||||
self._layout.addWidget(QLabel("Grid element size", parent=self), 3, 0)
|
||||
|
||||
self.width_enter = DbOverrideLineEdit(
|
||||
5, 100, default=self.__size_x, decimals=0, parent=self
|
||||
)
|
||||
self.width_enter = DbOverrideLineEdit(5, 100, default=self._size_x, decimals=0, parent=self)
|
||||
self.width_enter.valueChanged.connect(self.grid_size)
|
||||
self._register_override_field(self.width_enter)
|
||||
self._layout.addWidget(self.width_enter, 3, 1)
|
||||
@@ -60,7 +58,7 @@ class RasterDataCollectionPanel(ScanSettingsPanel):
|
||||
self._layout.addWidget(QLabel(" x ", parent=self), 3, 2)
|
||||
|
||||
self.height_enter = DbOverrideLineEdit(
|
||||
5, 100, default=self.__size_y, decimals=0, parent=self
|
||||
5, 100, default=self._size_y, decimals=0, parent=self
|
||||
)
|
||||
self.height_enter.valueChanged.connect(self.grid_size)
|
||||
self._register_override_field(self.height_enter)
|
||||
@@ -138,7 +136,7 @@ class RasterDataCollectionPanel(ScanSettingsPanel):
|
||||
self._layout.addItem(horizontal_spacer, 10, 0, 1, 5)
|
||||
|
||||
self._layout.addWidget(QLabel("Measurement time", parent=self), 11, 0)
|
||||
self.total_time = QLabel(f"{self.__total_time} min 0 s")
|
||||
self.total_time = QLabel(f"{self._total_time} min 0 s")
|
||||
self.total_time.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
|
||||
self._layout.addWidget(self.total_time, 11, 1, 1, 3)
|
||||
|
||||
@@ -158,9 +156,9 @@ class RasterDataCollectionPanel(ScanSettingsPanel):
|
||||
|
||||
@Slot(float)
|
||||
def grid_size(self, _: float):
|
||||
self.__size_x = self.width_enter.value
|
||||
self.__size_y = self.height_enter.value
|
||||
self.grid_size_updated.emit(self.__size_x / 1000.0, self.__size_y / 1000.0)
|
||||
self._size_x = self.width_enter.value
|
||||
self._size_y = self.height_enter.value
|
||||
self.grid_size_updated.emit(self._size_x / 1000.0, self._size_y / 1000.0)
|
||||
self.update_grid_scan_size()
|
||||
|
||||
@Slot(float)
|
||||
@@ -170,10 +168,10 @@ class RasterDataCollectionPanel(ScanSettingsPanel):
|
||||
|
||||
@Slot(int, int, float, float)
|
||||
def grid_scan_size_change(self, n_x: int, n_y: int, size_x_mm: float, size_y_mm: float):
|
||||
self.__size_x = size_x_mm * 1000.0
|
||||
self.__size_y = size_y_mm * 1000.0
|
||||
self.__n_x = n_x
|
||||
self.__n_y = n_y
|
||||
self._size_x = size_x_mm * 1000.0
|
||||
self._size_y = size_y_mm * 1000.0
|
||||
self._n_x = n_x
|
||||
self._n_y = n_y
|
||||
self.calculate_total_time()
|
||||
self.update_grid_scan_size()
|
||||
|
||||
@@ -193,17 +191,17 @@ class RasterDataCollectionPanel(ScanSettingsPanel):
|
||||
self._previous_sample_was_none_raster = True
|
||||
elif s.sample is not None:
|
||||
self._previous_sample_was_none_raster = False
|
||||
self.__beamline_state = s.state
|
||||
self._beamline_state = s.state
|
||||
|
||||
def update_grid_scan_size(self):
|
||||
# The live grid geometry is the "database"/system value for the element
|
||||
# size; a user override persists and can be reverted via the toggle.
|
||||
self.width_enter.set_db_value(self.__size_x)
|
||||
self.height_enter.set_db_value(self.__size_y)
|
||||
self.n_x_label.setText(str(self.__n_x))
|
||||
self.size_x_label.setText(f"{self.__size_x * self.__n_x:.1f}")
|
||||
self.n_y_label.setText(str(self.__n_y))
|
||||
self.size_y_label.setText(f"{self.__size_y * self.__n_y:.1f}")
|
||||
self.width_enter.set_db_value(self._size_x)
|
||||
self.height_enter.set_db_value(self._size_y)
|
||||
self.n_x_label.setText(str(self._n_x))
|
||||
self.size_x_label.setText(f"{self._size_x * self._n_x:.1f}")
|
||||
self.n_y_label.setText(str(self._n_y))
|
||||
self.size_y_label.setText(f"{self._size_y * self._n_y:.1f}")
|
||||
self.update_total_time_label()
|
||||
try:
|
||||
self.exp_time_s(self.image_time_enter.value)
|
||||
@@ -222,26 +220,26 @@ class RasterDataCollectionPanel(ScanSettingsPanel):
|
||||
]
|
||||
|
||||
def update_total_time_label(self):
|
||||
mins = int(self.__total_time // 60)
|
||||
secs = int(round(self.__total_time % 60))
|
||||
mins = int(self._total_time // 60)
|
||||
secs = int(round(self._total_time % 60))
|
||||
if secs == 60:
|
||||
mins += 1
|
||||
secs = 0
|
||||
self.total_time.setText(f"{mins} min {secs} s")
|
||||
|
||||
def calculate_total_time(self):
|
||||
if self.__n_x <= 0 or self.__n_y <= 0 or self.image_time_enter.value < 0:
|
||||
self.__total_time = 0.0
|
||||
if self._n_x <= 0 or self._n_y <= 0 or self.image_time_enter.value < 0:
|
||||
self._total_time = 0.0
|
||||
else:
|
||||
self.__total_time = self.__n_x * self.__n_y * self.image_time_enter.value * 1.3
|
||||
self._total_time = self._n_x * self._n_y * self.image_time_enter.value * 1.3
|
||||
# 30% buffer added
|
||||
# Show minutes
|
||||
self.update_total_time_label()
|
||||
|
||||
@Slot()
|
||||
def _on_evaluate_clicked(self):
|
||||
if self.__beamline_state != BeamlineStateEnum.SampleAlignment:
|
||||
logger.error(f"Beamline state {self.__beamline_state} is not Sample Alignment")
|
||||
if self._beamline_state != BeamlineStateEnum.SampleAlignment:
|
||||
logger.error(f"Beamline state {self._beamline_state} is not Sample Alignment")
|
||||
QMessageBox.critical(None, "Error", "Beamline state is not Sample Alignment")
|
||||
return
|
||||
if self.check_before_run(scan_kind="raster"):
|
||||
@@ -249,8 +247,8 @@ class RasterDataCollectionPanel(ScanSettingsPanel):
|
||||
|
||||
@Slot()
|
||||
def _on_evaluate_auto_clicked(self):
|
||||
if self.__beamline_state != BeamlineStateEnum.SampleAlignment:
|
||||
logger.error(f"Beamline state {self.__beamline_state} is not Sample Alignment")
|
||||
if self._beamline_state != BeamlineStateEnum.SampleAlignment:
|
||||
logger.error(f"Beamline state {self._beamline_state} is not Sample Alignment")
|
||||
QMessageBox.critical(None, "Error", "Beamline state is not Sample Alignment")
|
||||
return
|
||||
if self.check_before_run(scan_kind="raster"):
|
||||
|
||||
@@ -57,16 +57,16 @@ class ReferenceToolsModel(QAbstractTableModel):
|
||||
"Rotation count",
|
||||
"Screening count",
|
||||
]
|
||||
self.__sort_col = 0
|
||||
self.__sort_order = Qt.SortOrder.AscendingOrder
|
||||
self.__sorted_samples: list[SampleShortInfo] = []
|
||||
self._sort_col = 0
|
||||
self._sort_order = Qt.SortOrder.AscendingOrder
|
||||
self._sorted_samples: list[SampleShortInfo] = []
|
||||
if self.samples:
|
||||
self._sort()
|
||||
else:
|
||||
self.__sorted_samples = []
|
||||
self._sorted_samples = []
|
||||
|
||||
def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
|
||||
return len(self.__sorted_samples)
|
||||
return len(self._sorted_samples)
|
||||
|
||||
def columnCount(self, parent: QModelIndex = QModelIndex()) -> int:
|
||||
return len(self.header)
|
||||
@@ -75,15 +75,15 @@ class ReferenceToolsModel(QAbstractTableModel):
|
||||
if not index.isValid():
|
||||
return None
|
||||
|
||||
if index.row() >= len(self.__sorted_samples):
|
||||
if index.row() >= len(self._sorted_samples):
|
||||
return None
|
||||
|
||||
if role == Qt.ItemDataRole.DisplayRole:
|
||||
return get_entry(self.__sorted_samples[index.row()], index.column())
|
||||
return get_entry(self._sorted_samples[index.row()], index.column())
|
||||
elif role == Qt.ItemDataRole.TextAlignmentRole:
|
||||
return Qt.AlignmentFlag.AlignCenter
|
||||
elif role == Qt.ItemDataRole.BackgroundRole:
|
||||
if self.__sorted_samples[index.row()].db_id == self.current_reference:
|
||||
if self._sorted_samples[index.row()].db_id == self.current_reference:
|
||||
return QBrush(QColor(114, 159, 207)) # darker blue
|
||||
return QBrush(QColor(255, 255, 255)) # white
|
||||
|
||||
@@ -103,54 +103,54 @@ class ReferenceToolsModel(QAbstractTableModel):
|
||||
if self.samples: # Only sort if we have samples
|
||||
self._sort()
|
||||
else:
|
||||
self.__sorted_samples = [] # Clear sorted samples if no data
|
||||
self._sorted_samples = [] # Clear sorted samples if no data
|
||||
finally:
|
||||
self.endResetModel()
|
||||
|
||||
def sort(self, column, order):
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
self.__sort_order = order
|
||||
self.__sort_col = column
|
||||
self._sort_order = order
|
||||
self._sort_col = column
|
||||
self._sort()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def _sort(self):
|
||||
"""Sort samples, using special handling for location column."""
|
||||
if not self.samples:
|
||||
self.__sorted_samples = []
|
||||
self._sorted_samples = []
|
||||
return
|
||||
|
||||
if self.__sort_col == 0:
|
||||
if self._sort_col == 0:
|
||||
# Special sorting for location (Position column)
|
||||
self.__sorted_samples = sorted(
|
||||
self._sorted_samples = sorted(
|
||||
self.samples,
|
||||
key=lambda row: row.loc_str_sort(),
|
||||
reverse=(self.__sort_order == Qt.SortOrder.DescendingOrder),
|
||||
reverse=(self._sort_order == Qt.SortOrder.DescendingOrder),
|
||||
)
|
||||
elif self.__sort_col == 2:
|
||||
elif self._sort_col == 2:
|
||||
# Numeric sort for Mount count; place None last on ascending, first on descending
|
||||
none_sentinel = (
|
||||
float("inf") if self.__sort_order == Qt.SortOrder.AscendingOrder else float("-inf")
|
||||
float("inf") if self._sort_order == Qt.SortOrder.AscendingOrder else float("-inf")
|
||||
)
|
||||
self.__sorted_samples = sorted(
|
||||
self._sorted_samples = sorted(
|
||||
self.samples,
|
||||
key=lambda row: (
|
||||
row.mount_count if isinstance(row.mount_count, (int, float)) else none_sentinel
|
||||
),
|
||||
reverse=(self.__sort_order == Qt.SortOrder.DescendingOrder),
|
||||
reverse=(self._sort_order == Qt.SortOrder.DescendingOrder),
|
||||
)
|
||||
else:
|
||||
# String sort with empty fallback
|
||||
self.__sorted_samples = sorted(
|
||||
self._sorted_samples = sorted(
|
||||
self.samples,
|
||||
key=lambda row: get_entry(row, self.__sort_col) or "",
|
||||
reverse=(self.__sort_order == Qt.SortOrder.DescendingOrder),
|
||||
key=lambda row: get_entry(row, self._sort_col) or "",
|
||||
reverse=(self._sort_order == Qt.SortOrder.DescendingOrder),
|
||||
)
|
||||
|
||||
def get_item(self, row: int) -> Optional[SampleShortInfo]:
|
||||
"""Get sample at the given row index."""
|
||||
if 0 <= row < len(self.__sorted_samples):
|
||||
return self.__sorted_samples[row]
|
||||
if 0 <= row < len(self._sorted_samples):
|
||||
return self._sorted_samples[row]
|
||||
return None
|
||||
|
||||
def update_current_reference(self, reference_id: int | None):
|
||||
|
||||
@@ -48,13 +48,13 @@ class RotationDataCollectionPanel(ScanSettingsPanel):
|
||||
),
|
||||
)
|
||||
|
||||
self.__beamline_state = None
|
||||
self.__curr_pgroup = "p11206"
|
||||
self._beamline_state = None
|
||||
self._curr_pgroup = "p11206"
|
||||
self._filename = ""
|
||||
|
||||
self.__omega = 0
|
||||
self.__dose_mgy = 0
|
||||
self.__total_time = 0.0
|
||||
self._omega = 0
|
||||
self._dose_mgy = 0
|
||||
self._total_time = 0.0
|
||||
|
||||
self._layout.addWidget(QLabel("Start angle", parent=self), 3, 0)
|
||||
self.start_angle = NumberLineEdit(-720, 720.0, 0.0, decimals=3, parent=self)
|
||||
@@ -139,7 +139,7 @@ class RotationDataCollectionPanel(ScanSettingsPanel):
|
||||
self._register_override_field(self.image_time_enter)
|
||||
|
||||
self._layout.addWidget(QLabel("Total measurement time", parent=self), 13, 0)
|
||||
self.total_time = QLabel(f"{self.__total_time} min 0 s")
|
||||
self.total_time = QLabel(f"{self._total_time} min 0 s")
|
||||
self.total_time.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
|
||||
self._layout.addWidget(self.total_time, 13, 1, 1, 3)
|
||||
|
||||
@@ -150,7 +150,7 @@ class RotationDataCollectionPanel(ScanSettingsPanel):
|
||||
self.calculate_measurement_time()
|
||||
|
||||
self._layout.addWidget(QLabel("Dose", parent=self), 14, 0)
|
||||
self.dose = QLabel(f"{self.__dose_mgy}")
|
||||
self.dose = QLabel(f"{self._dose_mgy}")
|
||||
self.dose.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
|
||||
self._layout.addWidget(self.dose, 14, 1, 1, 3)
|
||||
self._layout.addWidget(QLabel("MGy", parent=self), 14, 4)
|
||||
@@ -167,8 +167,8 @@ class RotationDataCollectionPanel(ScanSettingsPanel):
|
||||
|
||||
@Slot()
|
||||
def run_screening(self):
|
||||
if self.__beamline_state != BeamlineStateEnum.SampleAlignment:
|
||||
logger.error(f"Beamline state {self.__beamline_state} is not Sample Alignment")
|
||||
if self._beamline_state != BeamlineStateEnum.SampleAlignment:
|
||||
logger.error(f"Beamline state {self._beamline_state} is not Sample Alignment")
|
||||
QMessageBox.critical(None, "Error", "Beamline state is not Sample Alignment")
|
||||
return
|
||||
if not self.check_before_run(scan_kind="screening"):
|
||||
@@ -210,8 +210,8 @@ class RotationDataCollectionPanel(ScanSettingsPanel):
|
||||
|
||||
@Slot()
|
||||
def run_measurement(self):
|
||||
if self.__beamline_state != BeamlineStateEnum.SampleAlignment:
|
||||
logger.error(f"Beamline state {self.__beamline_state} is not Sample Alignment")
|
||||
if self._beamline_state != BeamlineStateEnum.SampleAlignment:
|
||||
logger.error(f"Beamline state {self._beamline_state} is not Sample Alignment")
|
||||
QMessageBox.critical(None, "Error", "Beamline state is not Sample Alignment")
|
||||
return
|
||||
if not self.check_before_run(scan_kind="rotation"):
|
||||
@@ -243,8 +243,8 @@ class RotationDataCollectionPanel(ScanSettingsPanel):
|
||||
return round(total_angle / image_angle)
|
||||
|
||||
def update_total_time_label(self):
|
||||
mins = int(self.__total_time // 60)
|
||||
secs = int(round(self.__total_time % 60))
|
||||
mins = int(self._total_time // 60)
|
||||
secs = int(round(self._total_time % 60))
|
||||
if secs == 60:
|
||||
mins += 1
|
||||
secs = 0
|
||||
@@ -256,10 +256,10 @@ class RotationDataCollectionPanel(ScanSettingsPanel):
|
||||
or self.total_angle.value <= 0
|
||||
or self.image_time_enter.value < 0
|
||||
):
|
||||
self.__total_time = 0.0
|
||||
self._total_time = 0.0
|
||||
self.update_total_time_label()
|
||||
return
|
||||
self.__total_time = (
|
||||
self._total_time = (
|
||||
self.total_angle.value / self.image_angle.value
|
||||
) * self.image_time_enter.value
|
||||
self.update_total_time_label()
|
||||
@@ -271,7 +271,7 @@ class RotationDataCollectionPanel(ScanSettingsPanel):
|
||||
@Slot(DAQStatusModel)
|
||||
def update_daq_status(self, s: DAQStatusModel):
|
||||
super().update_daq_status(s)
|
||||
self.__omega = s.geom.omega_deg
|
||||
self._omega = s.geom.omega_deg
|
||||
|
||||
can_edit = getattr(self, "_can_edit_params", False)
|
||||
for w in (
|
||||
@@ -289,20 +289,20 @@ class RotationDataCollectionPanel(ScanSettingsPanel):
|
||||
|
||||
self.calculate_measurement_time()
|
||||
if s.session.current_pgroup:
|
||||
self.__curr_pgroup = s.session.current_pgroup
|
||||
self._curr_pgroup = s.session.current_pgroup
|
||||
lambda_a = s.diffraction.wavelength_angstrom
|
||||
kdose = 2000 / (lambda_a * lambda_a)
|
||||
beam_area = s.geom.beam_size_mm.x * s.geom.beam_size_mm.y * 1e6
|
||||
time = self.image_number() * self.image_time_enter.value
|
||||
|
||||
self.__dose_mgy = (time * s.bl.flux_ph_s * self._transmission) / (beam_area * kdose)
|
||||
self.dose.setText(f"{(self.__dose_mgy / 1e6):.1f}")
|
||||
self.__beamline_state = s.state
|
||||
self._dose_mgy = (time * s.bl.flux_ph_s * self._transmission) / (beam_area * kdose)
|
||||
self.dose.setText(f"{(self._dose_mgy / 1e6):.1f}")
|
||||
self._beamline_state = s.state
|
||||
|
||||
@Slot()
|
||||
def update_omega_start(self):
|
||||
if not self.start_angle.isReadOnly():
|
||||
self.start_angle.force_update_value(self.__omega)
|
||||
self.start_angle.force_update_value(self._omega)
|
||||
|
||||
def get_parameter_mappings(self):
|
||||
"""Return rotation-specific parameter mappings."""
|
||||
|
||||
@@ -46,7 +46,7 @@ class SamcamPanel(QWidget):
|
||||
self.exposure_spinbox.setStyleSheet("QDoubleSpinBox { background-color: white; }")
|
||||
self.exposure_spinbox.setDecimals(3)
|
||||
|
||||
self.exposure_spinbox.valueChanged.connect(self.__changed)
|
||||
self.exposure_spinbox.valueChanged.connect(self._changed)
|
||||
exposure_layout.addWidget(exposure_label)
|
||||
exposure_layout.addWidget(self.exposure_spinbox)
|
||||
|
||||
@@ -58,7 +58,7 @@ class SamcamPanel(QWidget):
|
||||
self.gain_spinbox.setSingleStep(1)
|
||||
self.gain_spinbox.setDecimals(1)
|
||||
self.gain_spinbox.setStyleSheet("QDoubleSpinBox { background-color: white; }")
|
||||
self.gain_spinbox.valueChanged.connect(self.__changed)
|
||||
self.gain_spinbox.valueChanged.connect(self._changed)
|
||||
gain_layout.addWidget(gain_label)
|
||||
gain_layout.addWidget(self.gain_spinbox)
|
||||
|
||||
@@ -84,7 +84,7 @@ class SamcamPanel(QWidget):
|
||||
screenshot_message_layout.addWidget(self.screenshot_message_edit)
|
||||
|
||||
self.screenshot_button = QPushButton("Take screenshot")
|
||||
self.screenshot_button.clicked.connect(self.__request_screenshot)
|
||||
self.screenshot_button.clicked.connect(self._request_screenshot)
|
||||
|
||||
# Show detections checkbox
|
||||
detections_layout = QHBoxLayout()
|
||||
|
||||
@@ -41,12 +41,12 @@ class SampleQueuePanel(QFrame):
|
||||
self, parent=None, samples: SampleShortInfoList | None = None, show_user: bool = False
|
||||
):
|
||||
super().__init__(parent)
|
||||
self.__baton_holder = None
|
||||
self.__busy = None
|
||||
self.__beamline_state = None
|
||||
self.__pause = True
|
||||
self.__set_to_pause = False
|
||||
self.__warning_msg_box = None
|
||||
self._baton_holder = None
|
||||
self._busy = None
|
||||
self._beamline_state = None
|
||||
self._pause = True
|
||||
self._set_to_pause = False
|
||||
self._warning_msg_box = None
|
||||
self._current_db_id: int | None = None
|
||||
self._step_through = False
|
||||
self.ring_current = None
|
||||
@@ -55,10 +55,10 @@ class SampleQueuePanel(QFrame):
|
||||
self._tell_connected = True
|
||||
self.loop_restart_requested = None
|
||||
|
||||
self.__recovery_timer = QTimer(self)
|
||||
self.__recovery_timer.setSingleShot(True)
|
||||
self.__recovery_timer.setInterval(600 * 1000) # 10 minutes
|
||||
self.__recovery_timer.timeout.connect(self.resume_automation)
|
||||
self._recovery_timer = QTimer(self)
|
||||
self._recovery_timer.setSingleShot(True)
|
||||
self._recovery_timer.setInterval(600 * 1000) # 10 minutes
|
||||
self._recovery_timer.timeout.connect(self.resume_automation)
|
||||
|
||||
self.setFrameShape(QFrame.Shape.StyledPanel)
|
||||
self.setFrameShadow(QFrame.Shadow.Raised)
|
||||
@@ -125,7 +125,7 @@ class SampleQueuePanel(QFrame):
|
||||
|
||||
def _samples_left_in_queue(self) -> int:
|
||||
total = len(self.table_model.samples)
|
||||
if self.__pause or total == 0:
|
||||
if self._pause or total == 0:
|
||||
return total
|
||||
return max(0, total - 1)
|
||||
|
||||
@@ -181,7 +181,7 @@ class SampleQueuePanel(QFrame):
|
||||
self.table_model.remove_sample(current.db_id)
|
||||
self._current_db_id = None
|
||||
|
||||
if not self.__pause and len(self.table_model.samples) > 0:
|
||||
if not self._pause and len(self.table_model.samples) > 0:
|
||||
next_item = self.table_model.samples[0]
|
||||
if self._step_through:
|
||||
self.pause_automation(set_id_to_None=False)
|
||||
@@ -229,11 +229,11 @@ class SampleQueuePanel(QFrame):
|
||||
return len(self._bad_conditions()) == 0
|
||||
|
||||
def resume_automation(self):
|
||||
if self.__warning_msg_box:
|
||||
self.__warning_msg_box.done(0)
|
||||
self.__warning_msg_box = None
|
||||
if self._warning_msg_box:
|
||||
self._warning_msg_box.done(0)
|
||||
self._warning_msg_box = None
|
||||
|
||||
if self.__pause and len(self.table_model.samples) > 0:
|
||||
if self._pause and len(self.table_model.samples) > 0:
|
||||
logger.info("Resuming automation after timeout")
|
||||
self.run()
|
||||
|
||||
@@ -242,34 +242,34 @@ class SampleQueuePanel(QFrame):
|
||||
self.resume_automation()
|
||||
|
||||
def show_warning_recovery_dialog(self):
|
||||
self.__warning_msg_box = QMessageBox(self)
|
||||
self.__warning_msg_box.setIcon(QMessageBox.Icon.Warning)
|
||||
self.__warning_msg_box.setWindowTitle("TELL Warning")
|
||||
self.__warning_msg_box.setText("TELL reported a warning. Please see console for details.")
|
||||
self.__warning_msg_box.setInformativeText(
|
||||
self._warning_msg_box = QMessageBox(self)
|
||||
self._warning_msg_box.setIcon(QMessageBox.Icon.Warning)
|
||||
self._warning_msg_box.setWindowTitle("TELL Warning")
|
||||
self._warning_msg_box.setText("TELL reported a warning. Please see console for details.")
|
||||
self._warning_msg_box.setInformativeText(
|
||||
"Automation paused for 10 minutes to allow sufficient dry time."
|
||||
"\nClick 'Continue Now' to resume immediately, or wait for auto-resume."
|
||||
)
|
||||
|
||||
continue_btn = self.__warning_msg_box.addButton(
|
||||
continue_btn = self._warning_msg_box.addButton(
|
||||
"Continue Now", QMessageBox.ButtonRole.AcceptRole
|
||||
)
|
||||
self.__warning_msg_box.setWindowModality(Qt.WindowModality.NonModal)
|
||||
self._warning_msg_box.setWindowModality(Qt.WindowModality.NonModal)
|
||||
continue_btn.clicked.connect(self.manual_resume_from_warning)
|
||||
self.__warning_msg_box.show()
|
||||
self._warning_msg_box.show()
|
||||
|
||||
def show_error_dialog(self, title: str, msg: str, info: str | None = None):
|
||||
self.__warning_msg_box = QMessageBox(self)
|
||||
self.__warning_msg_box.setIcon(QMessageBox.Icon.Warning)
|
||||
self.__warning_msg_box.setWindowTitle(title)
|
||||
self.__warning_msg_box.setText(msg)
|
||||
self._warning_msg_box = QMessageBox(self)
|
||||
self._warning_msg_box.setIcon(QMessageBox.Icon.Warning)
|
||||
self._warning_msg_box.setWindowTitle(title)
|
||||
self._warning_msg_box.setText(msg)
|
||||
if info:
|
||||
self.__warning_msg_box.setInformativeText(info)
|
||||
self.__warning_msg_box.setWindowModality(Qt.WindowModality.NonModal)
|
||||
self.__warning_msg_box.show()
|
||||
self._warning_msg_box.setInformativeText(info)
|
||||
self._warning_msg_box.setWindowModality(Qt.WindowModality.NonModal)
|
||||
self._warning_msg_box.show()
|
||||
|
||||
def is_running(self):
|
||||
return not self.__pause
|
||||
return not self._pause
|
||||
|
||||
def _finish_empty_queue(self):
|
||||
self.pause_automation()
|
||||
@@ -290,14 +290,14 @@ class SampleQueuePanel(QFrame):
|
||||
self.unmount.emit()
|
||||
|
||||
def run(self):
|
||||
self.__recovery_timer.stop()
|
||||
self._recovery_timer.stop()
|
||||
|
||||
# Pausing must ALWAYS be possible, regardless of beamline state, busy
|
||||
# flag, baton or maintenance. Only starting/resuming is gated on
|
||||
# conditions, so handle the pause toggle first and return immediately.
|
||||
if not self.__pause:
|
||||
self.__set_to_pause = True
|
||||
self.__pause = True
|
||||
if not self._pause:
|
||||
self._set_to_pause = True
|
||||
self._pause = True
|
||||
self.table_model.set_running(False)
|
||||
self.play_button.setText("▶ Run")
|
||||
self._emit_samples_in_queue_changed()
|
||||
@@ -307,11 +307,11 @@ class SampleQueuePanel(QFrame):
|
||||
# --- Starting/resuming: require the beamline to be in a good state. ---
|
||||
checks_enabled = self._checks_enabled()
|
||||
if checks_enabled:
|
||||
if self.__warning_msg_box:
|
||||
self.__warning_msg_box.done(0)
|
||||
self.__warning_msg_box = None
|
||||
if self._warning_msg_box:
|
||||
self._warning_msg_box.done(0)
|
||||
self._warning_msg_box = None
|
||||
|
||||
if self.__beamline_state != BeamlineStateEnum.SampleAlignment:
|
||||
if self._beamline_state != BeamlineStateEnum.SampleAlignment:
|
||||
self.show_error_dialog(
|
||||
title="wrong beamline state",
|
||||
msg="Cannot run automation while beamline is not in sample alignment state",
|
||||
@@ -319,8 +319,8 @@ class SampleQueuePanel(QFrame):
|
||||
)
|
||||
return
|
||||
|
||||
if self.__busy:
|
||||
logger.error(f"Cannot run automation while beamline is busy. Busy flag = {self.__busy}")
|
||||
if self._busy:
|
||||
logger.error(f"Cannot run automation while beamline is busy. Busy flag = {self._busy}")
|
||||
self.show_error_dialog(
|
||||
title="Beamline is busy",
|
||||
msg="Cannot run automation while beamline is busy",
|
||||
@@ -328,7 +328,7 @@ class SampleQueuePanel(QFrame):
|
||||
)
|
||||
return
|
||||
|
||||
if self.__baton_holder is SessionsStateEnum.Vacant:
|
||||
if self._baton_holder is SessionsStateEnum.Vacant:
|
||||
self.show_error_dialog(
|
||||
title="Session is vacant",
|
||||
msg="Starting automation while session is vacant is not currently implemented",
|
||||
@@ -337,7 +337,7 @@ class SampleQueuePanel(QFrame):
|
||||
)
|
||||
return
|
||||
|
||||
elif self.__baton_holder is not SessionsStateEnum.OwnedByYou:
|
||||
elif self._baton_holder is not SessionsStateEnum.OwnedByYou:
|
||||
self.show_error_dialog(
|
||||
title="You do not hold the baton",
|
||||
msg="You do not hold the baton.",
|
||||
@@ -347,7 +347,7 @@ class SampleQueuePanel(QFrame):
|
||||
)
|
||||
return
|
||||
|
||||
if self.__beamline_state is BeamlineStateEnum.Maintenance:
|
||||
if self._beamline_state is BeamlineStateEnum.Maintenance:
|
||||
self.show_error_dialog(
|
||||
title="Maintenance mode",
|
||||
msg="Cannot run automation while beamline is in maintenance mode",
|
||||
@@ -371,8 +371,8 @@ class SampleQueuePanel(QFrame):
|
||||
return
|
||||
|
||||
self.table_model.set_running(True)
|
||||
self.__set_to_pause = False
|
||||
self.__pause = False
|
||||
self._set_to_pause = False
|
||||
self._pause = False
|
||||
self.play_button.setText("⏸ Pause")
|
||||
current = self.table_model.samples[0]
|
||||
self._current_db_id = current.db_id
|
||||
@@ -400,13 +400,13 @@ class SampleQueuePanel(QFrame):
|
||||
self._experiment_shutter_state = s.bl.exp_shutter_open
|
||||
self._door_prohibited = getattr(s.bl, "pss_prohibited", None)
|
||||
self._tell_connected = getattr(s, "tell_connected", True)
|
||||
self.__beamline_state = s.state
|
||||
self.__busy = s.busy
|
||||
self.__baton_holder = s.session.session
|
||||
self._beamline_state = s.state
|
||||
self._busy = s.busy
|
||||
self._baton_holder = s.session.session
|
||||
|
||||
def pause_automation(self, set_id_to_None: bool = True):
|
||||
self.table_model.set_running(False)
|
||||
self.__pause = True
|
||||
self._pause = True
|
||||
self.play_button.setText("▶ Run")
|
||||
if set_id_to_None:
|
||||
self._current_db_id = None
|
||||
@@ -427,7 +427,7 @@ class SampleQueuePanel(QFrame):
|
||||
if conditions_auto_check(self, msg, self._conditions_ok):
|
||||
logger.debug("Conditions recovered or user chose to continue; resuming automation")
|
||||
self.table_model.set_running(True)
|
||||
self.__pause = False
|
||||
self._pause = False
|
||||
self.play_button.setText("⏸ Pause")
|
||||
self._emit_samples_in_queue_changed()
|
||||
else:
|
||||
@@ -440,7 +440,7 @@ class SampleQueuePanel(QFrame):
|
||||
self.table_model.remove_sample(db_id)
|
||||
self._current_db_id = None
|
||||
|
||||
if not self.__pause and len(self.table_model.samples) > 0:
|
||||
if not self._pause and len(self.table_model.samples) > 0:
|
||||
next_item = self.table_model.samples[0]
|
||||
if self._step_through:
|
||||
self.pause_automation(set_id_to_None=False)
|
||||
@@ -457,7 +457,7 @@ class SampleQueuePanel(QFrame):
|
||||
self._current_db_id = None
|
||||
self.pause_automation()
|
||||
logger.warning("TELL Warning: Pausing for 10 minutes.")
|
||||
self.__recovery_timer.start()
|
||||
self._recovery_timer.start()
|
||||
self.show_warning_recovery_dialog()
|
||||
|
||||
elif reply == "Critical":
|
||||
@@ -485,7 +485,7 @@ class SampleQueuePanel(QFrame):
|
||||
else:
|
||||
self.table_model.remove_sample(db_id)
|
||||
self._current_db_id = None
|
||||
if not self.__pause and len(self.table_model.samples) > 0:
|
||||
if not self._pause and len(self.table_model.samples) > 0:
|
||||
next_item = self.table_model.samples[0]
|
||||
if self._step_through:
|
||||
self.pause_automation(set_id_to_None=False)
|
||||
|
||||
@@ -35,7 +35,7 @@ class ScanSettingsPanel(QWidget):
|
||||
parent=None,
|
||||
):
|
||||
super().__init__(parent)
|
||||
self.__diffraction = diffraction
|
||||
self._diffraction = diffraction
|
||||
|
||||
self._transmission = default_transmission
|
||||
self._dtz = default_dtz
|
||||
@@ -167,10 +167,10 @@ class ScanSettingsPanel(QWidget):
|
||||
def update_daq_status(self, s: DAQStatusModel):
|
||||
self.dtz_enter.update_limits(s.bl.dtz_min, s.bl.dtz_max)
|
||||
self.high_res_enter.update_limits(
|
||||
self.__diffraction.resolution_angstrom(s.bl.dtz_min),
|
||||
self.__diffraction.resolution_angstrom(s.bl.dtz_max),
|
||||
self._diffraction.resolution_angstrom(s.bl.dtz_min),
|
||||
self._diffraction.resolution_angstrom(s.bl.dtz_max),
|
||||
)
|
||||
self.__diffraction = s.diffraction
|
||||
self._diffraction = s.diffraction
|
||||
self._ring_current = s.bl.ring_current_mA
|
||||
self._experiment_shutter_state = s.bl.exp_shutter_open
|
||||
self._door_prohibited = getattr(s.bl, "pss_prohibited", None)
|
||||
@@ -214,10 +214,10 @@ class ScanSettingsPanel(QWidget):
|
||||
self._high_res = float(self.high_res_enter.value)
|
||||
|
||||
def _dtz_to_res(self, dtz: float) -> float:
|
||||
return self.__diffraction.resolution_angstrom(dtz)
|
||||
return self._diffraction.resolution_angstrom(dtz)
|
||||
|
||||
def _res_to_dtz(self, res: float) -> float:
|
||||
dtz = self.__diffraction.calc_dtz_mm(res)
|
||||
dtz = self._diffraction.calc_dtz_mm(res)
|
||||
return self.MIN_DTZ if dtz < self.MIN_DTZ else dtz
|
||||
|
||||
@Slot(float)
|
||||
|
||||
@@ -53,8 +53,8 @@ class SmargonPanel(QWidget):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.__geom: SampleGeometryModel | None = None
|
||||
self.__step = 100
|
||||
self._geom: SampleGeometryModel | None = None
|
||||
self._step = 100
|
||||
|
||||
grid_layout = QGridLayout(self)
|
||||
|
||||
@@ -89,7 +89,7 @@ class SmargonPanel(QWidget):
|
||||
|
||||
@Slot(float)
|
||||
def step_changed(self, f: float):
|
||||
self.__step = f
|
||||
self._step = f
|
||||
|
||||
@Slot()
|
||||
def home(self):
|
||||
@@ -108,13 +108,13 @@ class SmargonPanel(QWidget):
|
||||
def update_daq_status(self, s: DAQStatusModel):
|
||||
self.chi_enter.update_value(s.geom.smargon.chi_deg)
|
||||
self.phi_enter.update_value(s.geom.smargon.phi_deg)
|
||||
self.__geom = s.geom
|
||||
self._geom = s.geom
|
||||
|
||||
@Slot(Coordinate)
|
||||
def smargon_rel(self, c: Coordinate):
|
||||
if self.__geom is None:
|
||||
if self._geom is None:
|
||||
return
|
||||
|
||||
self.smargon.emit(
|
||||
SmargonCoordinate(sh_mm=self.__geom.beamline_to_smargon(c * self.__step / 1000.0))
|
||||
SmargonCoordinate(sh_mm=self._geom.beamline_to_smargon(c * self._step / 1000.0))
|
||||
)
|
||||
|
||||
@@ -33,9 +33,9 @@ class SimpleRotationSettingsPanel(QWidget):
|
||||
self._filename = ""
|
||||
self.dtz = 120
|
||||
self.image_time_s = 0.1
|
||||
self.__d = None
|
||||
self._d = None
|
||||
self._temperature = 100
|
||||
self.__omega = 0
|
||||
self._omega = 0
|
||||
self._wilson_b = None
|
||||
self.flux_ph_s = None
|
||||
self.total_time_s = 0
|
||||
@@ -211,12 +211,12 @@ class SimpleRotationSettingsPanel(QWidget):
|
||||
|
||||
@Slot(DAQStatusModel)
|
||||
def update_daq_status(self, s: DAQStatusModel):
|
||||
self.__d = s
|
||||
self._d = s
|
||||
# TODO only update best_res after raster finished otherwise ask to update. or have toggle to overwrite with user value
|
||||
# TODO only take best res from flat face scan
|
||||
# TODO identify flat face!!!!
|
||||
best_res = s.last_best_res
|
||||
self.__omega = s.geom.omega_deg
|
||||
self._omega = s.geom.omega_deg
|
||||
|
||||
if best_res is not None:
|
||||
# clamp to control limits and update field; this will also trigger recalculation
|
||||
@@ -274,7 +274,7 @@ class SimpleRotationSettingsPanel(QWidget):
|
||||
|
||||
@Slot()
|
||||
def update_omega_start(self):
|
||||
self.start_angle_enter.update_value(self.__omega)
|
||||
self.start_angle_enter.update_value(self._omega)
|
||||
|
||||
def update_total_time_label(self):
|
||||
mins = int(self.total_time_s // 60)
|
||||
@@ -285,7 +285,7 @@ class SimpleRotationSettingsPanel(QWidget):
|
||||
self.total_time.setText(f"{mins} min {secs} s")
|
||||
|
||||
def update_calculated_labels(self):
|
||||
if self.__d is None:
|
||||
if self._d is None:
|
||||
return
|
||||
flux = 2.5e11 # TODO link flux
|
||||
# TODO add start angle
|
||||
@@ -301,10 +301,10 @@ class SimpleRotationSettingsPanel(QWidget):
|
||||
|
||||
self.target_res_label.setText(f"{d_tar:.2f}")
|
||||
|
||||
Kdose = 2000 / (self.__d.diffraction.wavelength_angstrom**2)
|
||||
Kdose = 2000 / (self._d.diffraction.wavelength_angstrom**2)
|
||||
|
||||
beam_size_um_y = self.__d.geom.beam_size_mm.y * 1000
|
||||
beam_size_um_x = self.__d.geom.beam_size_mm.x * 1000
|
||||
beam_size_um_y = self._d.geom.beam_size_mm.y * 1000
|
||||
beam_size_um_x = self._d.geom.beam_size_mm.x * 1000
|
||||
self.dose_rate_MGy_s = (flux / (beam_size_um_x * beam_size_um_y * Kdose)) / 1e6
|
||||
if (
|
||||
self.xtal_y is None
|
||||
@@ -348,7 +348,7 @@ class SimpleRotationSettingsPanel(QWidget):
|
||||
else:
|
||||
self.transmission = 1.0
|
||||
|
||||
self.dtz = self.__d.diffraction.calc_dtz_mm(d_tar)
|
||||
self.dtz = self._d.diffraction.calc_dtz_mm(d_tar)
|
||||
if self.dtz < 108:
|
||||
self.dtz = 108
|
||||
|
||||
@@ -357,9 +357,9 @@ class SimpleRotationSettingsPanel(QWidget):
|
||||
|
||||
if self.dtz <= 0.0:
|
||||
self.dtz_label.setText("""<span style="color: red ; ">-</span>""")
|
||||
elif self.dtz < self.__d.bl.dtz_min:
|
||||
elif self.dtz < self._d.bl.dtz_min:
|
||||
self.dtz_label.setText(f"""<span style="color: red ; ">{self.dtz:.2f}</span>""")
|
||||
self.dtz = self.__d.bl.dtz_min
|
||||
self.dtz = self._d.bl.dtz_min
|
||||
else:
|
||||
self.dtz_label.setText(f"{self.dtz:.2f}")
|
||||
|
||||
|
||||
@@ -246,5 +246,5 @@ class TellSamplePanel(QFrame):
|
||||
self.curr_sample_label.setText(base_text)
|
||||
|
||||
if status.session.current_pgroup is not None:
|
||||
self.__current_pgroup = status.session.current_pgroup
|
||||
self.table_model.set_default_user_filter(self.__current_pgroup)
|
||||
self._current_pgroup = status.session.current_pgroup
|
||||
self.table_model.set_default_user_filter(self._current_pgroup)
|
||||
|
||||
@@ -12,10 +12,10 @@ class ZoomPanel(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.__zoom = 1.0
|
||||
self._zoom = 1.0
|
||||
|
||||
grid_layout = QGridLayout(self)
|
||||
self.__zoom_settings = [
|
||||
self._zoom_settings = [
|
||||
{"name": "1.0x", "value": 1},
|
||||
{"name": "2.0x", "value": 280},
|
||||
{"name": "3.5x", "value": 500},
|
||||
@@ -26,13 +26,13 @@ class ZoomPanel(QWidget):
|
||||
grid_layout.addWidget(TitleLabel("Zoom", self), 0, 0, 1, 2)
|
||||
i = 2
|
||||
|
||||
self.__buttons = []
|
||||
for z in self.__zoom_settings:
|
||||
self._buttons = []
|
||||
for z in self._zoom_settings:
|
||||
button = ButtonWithPayload(z["name"], {"zoom": z["value"]})
|
||||
grid_layout.addWidget(button, int(i / 2), int(i % 2))
|
||||
i += 1
|
||||
button.pressed.connect(self.set_zoom)
|
||||
self.__buttons.append(button)
|
||||
self._buttons.append(button)
|
||||
|
||||
@Slot(dict)
|
||||
def set_zoom(self, payload: dict):
|
||||
@@ -40,9 +40,9 @@ class ZoomPanel(QWidget):
|
||||
|
||||
@Slot(DAQStatusModel)
|
||||
def update_daq_status(self, status: DAQStatusModel):
|
||||
self.__zoom = status.bl.zoom
|
||||
for z in self.__zoom_settings:
|
||||
if z["value"] == self.__zoom:
|
||||
self.__buttons[self.__zoom_settings.index(z)].setStyleSheet("font-weight: bold;")
|
||||
self._zoom = status.bl.zoom
|
||||
for z in self._zoom_settings:
|
||||
if z["value"] == self._zoom:
|
||||
self._buttons[self._zoom_settings.index(z)].setStyleSheet("font-weight: bold;")
|
||||
else:
|
||||
self.__buttons[self.__zoom_settings.index(z)].setStyleSheet("")
|
||||
self._buttons[self._zoom_settings.index(z)].setStyleSheet("")
|
||||
|
||||
@@ -3,7 +3,7 @@ from enum import Enum
|
||||
from typing import List, Tuple
|
||||
|
||||
import numpy as np
|
||||
from aarecommon.config.beamline import get_jfjoch_url, mx_beamline, cfg_get
|
||||
from aarecommon.config.beamline import cfg_get, get_jfjoch_url, mx_beamline
|
||||
from aarecommon.config.logger import setup_logger
|
||||
from aarecommon.math.coordinate import Coordinate, SmargonCoordinate
|
||||
from aarecommon.math.find_xtal import compute_crystal_score_array
|
||||
@@ -121,25 +121,25 @@ class RasterGridManager(QObject):
|
||||
def __init__(self, geom: SampleGeometryModel):
|
||||
super().__init__()
|
||||
|
||||
self.__sample_id = -1
|
||||
self.__beamline = mx_beamline()
|
||||
self.__detector_url = get_jfjoch_url(self.__beamline)
|
||||
self._sample_id = -1
|
||||
self._beamline = mx_beamline()
|
||||
self._detector_url = get_jfjoch_url(self._beamline)
|
||||
|
||||
self.__beam_size_mm = geom.beam_size_mm
|
||||
self.__geom = geom
|
||||
self._beam_size_mm = geom.beam_size_mm
|
||||
self._geom = geom
|
||||
|
||||
self.__metric = RasterGridMetric.RASTER_SCORE
|
||||
self._metric = RasterGridMetric.RASTER_SCORE
|
||||
|
||||
self.__loaded_image_prefix = None
|
||||
self.__loaded_image_index = None
|
||||
self._loaded_image_prefix = None
|
||||
self._loaded_image_index = None
|
||||
|
||||
self.__start_point: QPointF = QPointF(0, 0)
|
||||
self.__active_grid: RasterGridRequest = RasterGridRequest(
|
||||
self._start_point: QPointF = QPointF(0, 0)
|
||||
self._active_grid: RasterGridRequest = RasterGridRequest(
|
||||
n_x=0,
|
||||
n_y=0,
|
||||
smargon_top_left=self.__geom.smargon,
|
||||
grid_size_mm=Coordinate(x=0.8 * self.__beam_size_mm.x, y=0.8 * self.__beam_size_mm.y),
|
||||
omega_deg=self.__geom.omega_deg,
|
||||
smargon_top_left=self._geom.smargon,
|
||||
grid_size_mm=Coordinate(x=0.8 * self._beam_size_mm.x, y=0.8 * self._beam_size_mm.y),
|
||||
omega_deg=self._geom.omega_deg,
|
||||
exp_time_s=cfg_get(
|
||||
"daq.data_collection_settings.default_raster_settings.exp_time_s", 0.01
|
||||
),
|
||||
@@ -148,24 +148,24 @@ class RasterGridManager(QObject):
|
||||
),
|
||||
dtz=cfg_get("daq.data_collection_settings.default_raster_settings.dtz", 200.0),
|
||||
)
|
||||
self.__completed_grids: List[CompletedRasterGridElem] = []
|
||||
self._completed_grids: List[CompletedRasterGridElem] = []
|
||||
|
||||
# Cache of pre-rendered heatmap bitmaps, keyed by (id(grid_elem), metric).
|
||||
# Each entry is (QImage, backing ndarray); the ndarray must be kept alive
|
||||
# because QImage shares its buffer without copying. Rebuilt only when the
|
||||
# data or metric changes, not on every repaint (sample move / zoom).
|
||||
self.__heatmap_cache: dict[tuple[int, "RasterGridMetric"], tuple[QImage, np.ndarray]] = {}
|
||||
self._heatmap_cache: dict[tuple[int, "RasterGridMetric"], tuple[QImage, np.ndarray]] = {}
|
||||
|
||||
@property
|
||||
def active_grid(self) -> RasterGridRequest:
|
||||
return self.__active_grid
|
||||
return self._active_grid
|
||||
|
||||
def _is_grid_visible(self, grid: RasterGridRequest):
|
||||
if (
|
||||
grid.visible
|
||||
and abs(normalize_angle(grid.omega_deg - self.__geom.omega_deg)) < 0.2
|
||||
and abs(grid.smargon_top_left.phi_deg - self.__geom.smargon.phi_deg) < 0.2
|
||||
and abs(grid.smargon_top_left.chi_deg - self.__geom.smargon.chi_deg) < 0.2
|
||||
and abs(normalize_angle(grid.omega_deg - self._geom.omega_deg)) < 0.2
|
||||
and abs(grid.smargon_top_left.phi_deg - self._geom.smargon.phi_deg) < 0.2
|
||||
and abs(grid.smargon_top_left.chi_deg - self._geom.smargon.chi_deg) < 0.2
|
||||
and grid.n_x > 0
|
||||
and grid.n_y > 0
|
||||
):
|
||||
@@ -178,15 +178,15 @@ class RasterGridManager(QObject):
|
||||
if not self._is_grid_visible(grid):
|
||||
return None
|
||||
|
||||
cell = grid.grid_size_pxl(self.__geom)
|
||||
cell = grid.grid_size_pxl(self._geom)
|
||||
cell_w = float(cell.x)
|
||||
cell_h = float(cell.y)
|
||||
|
||||
if cell_w <= 0 or cell_h <= 0:
|
||||
return None
|
||||
|
||||
start_anchor = grid.start_pxl(self.__geom)
|
||||
start_picture = self.__geom.sample_to_picture(start_anchor)
|
||||
start_anchor = grid.start_pxl(self._geom)
|
||||
start_picture = self._geom.sample_to_picture(start_anchor)
|
||||
|
||||
return float(start_picture.x), float(start_picture.y), cell_w, cell_h
|
||||
|
||||
@@ -219,91 +219,91 @@ class RasterGridManager(QObject):
|
||||
|
||||
@Slot(RasterGridRequest)
|
||||
def update_active_grid_request(self, grid: RasterGridRequest):
|
||||
self.__active_grid.smargon_top_left = grid.smargon_top_left
|
||||
self.__active_grid.omega_deg = grid.omega_deg
|
||||
self.__active_grid.grid_size_mm = grid.grid_size_mm
|
||||
self.__active_grid.n_x = grid.n_x
|
||||
self.__active_grid.n_y = grid.n_y
|
||||
self._active_grid.smargon_top_left = grid.smargon_top_left
|
||||
self._active_grid.omega_deg = grid.omega_deg
|
||||
self._active_grid.grid_size_mm = grid.grid_size_mm
|
||||
self._active_grid.n_x = grid.n_x
|
||||
self._active_grid.n_y = grid.n_y
|
||||
self.grid_scan_size_changed.emit(
|
||||
self.__active_grid.n_x,
|
||||
self.__active_grid.n_y,
|
||||
self.__active_grid.grid_size_mm.x,
|
||||
self.__active_grid.grid_size_mm.y,
|
||||
self._active_grid.n_x,
|
||||
self._active_grid.n_y,
|
||||
self._active_grid.grid_size_mm.x,
|
||||
self._active_grid.grid_size_mm.y,
|
||||
)
|
||||
|
||||
@Slot(DAQStatusModel)
|
||||
def update_daq_status(self, s: DAQStatusModel):
|
||||
if s.sample is not None and s.sample.db_id != self.__sample_id:
|
||||
self.__sample_id = s.sample.db_id
|
||||
if s.sample is not None and s.sample.db_id != self._sample_id:
|
||||
self._sample_id = s.sample.db_id
|
||||
self.clear_completed_grids()
|
||||
self.clear_active_grid()
|
||||
if self.__beam_size_mm != s.geom.beam_size_mm:
|
||||
self.__beam_size_mm = s.geom.beam_size_mm
|
||||
self.update_grid_size(0.8 * self.__beam_size_mm.x, 0.8 * self.__beam_size_mm.y)
|
||||
self.__geom = s.geom
|
||||
if self._beam_size_mm != s.geom.beam_size_mm:
|
||||
self._beam_size_mm = s.geom.beam_size_mm
|
||||
self.update_grid_size(0.8 * self._beam_size_mm.x, 0.8 * self._beam_size_mm.y)
|
||||
self._geom = s.geom
|
||||
|
||||
def resize_active_grid(self, end_point: QPointF):
|
||||
self.update_active_grid(self.__start_point, end_point)
|
||||
self.update_active_grid(self._start_point, end_point)
|
||||
|
||||
def update_active_grid(self, start: QPointF, end: QPointF):
|
||||
delta_x = (end.x() - start.x()) * self.__geom.pixel_in_mm
|
||||
delta_y = (end.y() - start.y()) * self.__geom.pixel_in_mm
|
||||
delta_x = (end.x() - start.x()) * self._geom.pixel_in_mm
|
||||
delta_y = (end.y() - start.y()) * self._geom.pixel_in_mm
|
||||
|
||||
self.__active_grid.n_x = round(abs(delta_x / self.__active_grid.grid_size_mm.x))
|
||||
self.__active_grid.n_y = round(abs(delta_y / self.__active_grid.grid_size_mm.y))
|
||||
self._active_grid.n_x = round(abs(delta_x / self._active_grid.grid_size_mm.x))
|
||||
self._active_grid.n_y = round(abs(delta_y / self._active_grid.grid_size_mm.y))
|
||||
|
||||
c = self.__geom.picture_to_sample(Coordinate(x=start.x(), y=start.y()))
|
||||
c = self._geom.picture_to_sample(Coordinate(x=start.x(), y=start.y()))
|
||||
|
||||
if delta_x < 0:
|
||||
c.x -= self.__active_grid.n_x * self.__active_grid.grid_size_mm.x
|
||||
c.x -= self._active_grid.n_x * self._active_grid.grid_size_mm.x
|
||||
if delta_y < 0:
|
||||
c.y -= self.__active_grid.n_y * self.__active_grid.grid_size_mm.y
|
||||
c.y -= self._active_grid.n_y * self._active_grid.grid_size_mm.y
|
||||
|
||||
if delta_x < 0 or delta_y < 0:
|
||||
c_pic = self.__geom.sample_to_picture(c)
|
||||
self.__start_point = QPointF(c_pic.x, c_pic.y)
|
||||
c_pic = self._geom.sample_to_picture(c)
|
||||
self._start_point = QPointF(c_pic.x, c_pic.y)
|
||||
else:
|
||||
self.__start_point = start
|
||||
self._start_point = start
|
||||
|
||||
self.__active_grid.smargon_top_left = SmargonCoordinate(
|
||||
sh_mm=self.__geom.beamline_to_smargon(c),
|
||||
phi_deg=self.__geom.smargon.phi_deg,
|
||||
chi_deg=self.__geom.smargon.chi_deg,
|
||||
self._active_grid.smargon_top_left = SmargonCoordinate(
|
||||
sh_mm=self._geom.beamline_to_smargon(c),
|
||||
phi_deg=self._geom.smargon.phi_deg,
|
||||
chi_deg=self._geom.smargon.chi_deg,
|
||||
)
|
||||
|
||||
self.__active_grid.omega_deg = self.__geom.omega_deg
|
||||
self._active_grid.omega_deg = self._geom.omega_deg
|
||||
self.grid_scan_size_changed.emit(
|
||||
self.__active_grid.n_x,
|
||||
self.__active_grid.n_y,
|
||||
self.__active_grid.grid_size_mm.x,
|
||||
self.__active_grid.grid_size_mm.y,
|
||||
self._active_grid.n_x,
|
||||
self._active_grid.n_y,
|
||||
self._active_grid.grid_size_mm.x,
|
||||
self._active_grid.grid_size_mm.y,
|
||||
)
|
||||
|
||||
def move_active_grid(self, delta: QPointF):
|
||||
if not self._is_grid_visible(self.__active_grid):
|
||||
if not self._is_grid_visible(self._active_grid):
|
||||
return
|
||||
|
||||
delta_pxl = Coordinate(x=delta.x(), y=delta.y())
|
||||
delta_mm = self.__geom.smargon_nudge(delta_pxl * self.__geom.pixel_in_mm)
|
||||
delta_mm = self._geom.smargon_nudge(delta_pxl * self._geom.pixel_in_mm)
|
||||
|
||||
self.__active_grid.smargon_top_left = SmargonCoordinate(
|
||||
sh_mm=self.__active_grid.smargon_top_left.sh_mm + delta_mm,
|
||||
phi_deg=self.__geom.smargon.phi_deg,
|
||||
chi_deg=self.__geom.smargon.chi_deg,
|
||||
self._active_grid.smargon_top_left = SmargonCoordinate(
|
||||
sh_mm=self._active_grid.smargon_top_left.sh_mm + delta_mm,
|
||||
phi_deg=self._geom.smargon.phi_deg,
|
||||
chi_deg=self._geom.smargon.chi_deg,
|
||||
)
|
||||
|
||||
@Slot()
|
||||
def clear_active_grid(self):
|
||||
self.__active_grid.n_x = 0
|
||||
self.__active_grid.n_y = 0
|
||||
self._active_grid.n_x = 0
|
||||
self._active_grid.n_y = 0
|
||||
self.grid_scan_size_changed.emit(
|
||||
0, 0, self.__active_grid.grid_size_mm.x, self.__active_grid.grid_size_mm.y
|
||||
0, 0, self._active_grid.grid_size_mm.x, self._active_grid.grid_size_mm.y
|
||||
)
|
||||
|
||||
def get_grid_coord(self, grid: RasterGridRequest, point: QPointF) -> Tuple[int, int]:
|
||||
point_bl = self.__geom.picture_to_sample(Coordinate(x=point.x(), y=point.y()))
|
||||
point_bl = self._geom.picture_to_sample(Coordinate(x=point.x(), y=point.y()))
|
||||
|
||||
delta = point_bl - self.__geom.smargon_to_beamline(grid.smargon_top_left.sh_mm)
|
||||
delta = point_bl - self._geom.smargon_to_beamline(grid.smargon_top_left.sh_mm)
|
||||
|
||||
elem_x = grid.grid_size_mm.x
|
||||
elem_y = grid.grid_size_mm.y
|
||||
@@ -315,7 +315,7 @@ class RasterGridManager(QObject):
|
||||
|
||||
@Slot(QPointF)
|
||||
def load_image(self, point: QPointF):
|
||||
for grid in self.__completed_grids:
|
||||
for grid in self._completed_grids:
|
||||
if self._is_grid_visible(grid.request):
|
||||
n_x = grid.request.n_x
|
||||
n_y = grid.request.n_y
|
||||
@@ -325,11 +325,11 @@ class RasterGridManager(QObject):
|
||||
cell = cell_y * n_x + cell_x
|
||||
|
||||
if (
|
||||
self.__loaded_image_prefix != grid.result.file_prefix
|
||||
or self.__loaded_image_index != grid.result.images[cell].number
|
||||
self._loaded_image_prefix != grid.result.file_prefix
|
||||
or self._loaded_image_index != grid.result.images[cell].number
|
||||
):
|
||||
self.__loaded_image_prefix = grid.result.file_prefix
|
||||
self.__loaded_image_index = grid.result.images[cell].number
|
||||
self._loaded_image_prefix = grid.result.file_prefix
|
||||
self._loaded_image_index = grid.result.images[cell].number
|
||||
logger.debug(
|
||||
f"Load {grid.result.file_prefix} {grid.result.images[cell].number}"
|
||||
)
|
||||
@@ -339,22 +339,20 @@ class RasterGridManager(QObject):
|
||||
# TODO if not in the same PGroup, user can stream from last run only but never load from a file.
|
||||
# If loading a prior run, this shoudl throw an error,
|
||||
# if user in same pgroup, load will always be fine.
|
||||
# self.image_selected.emit(self.__detector_url, grid.result.images[cell].number)
|
||||
# self.image_selected.emit(self._detector_url, grid.result.images[cell].number)
|
||||
|
||||
def is_part_of_active_grid(self, point: QPointF) -> bool:
|
||||
if not self._is_grid_visible(self.__active_grid):
|
||||
if not self._is_grid_visible(self._active_grid):
|
||||
return False
|
||||
point_bl = self.__geom.picture_to_sample(Coordinate(x=point.x(), y=point.y()))
|
||||
point_bl = self._geom.picture_to_sample(Coordinate(x=point.x(), y=point.y()))
|
||||
|
||||
delta = point_bl - self.__geom.smargon_to_beamline(
|
||||
self.__active_grid.smargon_top_left.sh_mm
|
||||
)
|
||||
return (0 <= delta.x < self.__active_grid.n_x * self.__active_grid.grid_size_mm.x) and (
|
||||
0 <= delta.y < self.__active_grid.n_y * self.__active_grid.grid_size_mm.y
|
||||
delta = point_bl - self._geom.smargon_to_beamline(self._active_grid.smargon_top_left.sh_mm)
|
||||
return (0 <= delta.x < self._active_grid.n_x * self._active_grid.grid_size_mm.x) and (
|
||||
0 <= delta.y < self._active_grid.n_y * self._active_grid.grid_size_mm.y
|
||||
)
|
||||
|
||||
def is_part_of_completed_grid(self, point: QPointF) -> str | None:
|
||||
for grid in self.__completed_grids:
|
||||
for grid in self._completed_grids:
|
||||
if self._is_grid_visible(grid.request):
|
||||
n_x = grid.request.n_x
|
||||
n_y = grid.request.n_y
|
||||
@@ -432,25 +430,25 @@ class RasterGridManager(QObject):
|
||||
if (grid_size_mm_x <= 0) or (grid_size_mm_y <= 0):
|
||||
raise ValueError("Grid size must be positive")
|
||||
|
||||
self.__active_grid.n_x = round(
|
||||
self.__active_grid.n_x * self.__active_grid.grid_size_mm.x / grid_size_mm_x
|
||||
self._active_grid.n_x = round(
|
||||
self._active_grid.n_x * self._active_grid.grid_size_mm.x / grid_size_mm_x
|
||||
)
|
||||
self.__active_grid.n_y = round(
|
||||
self.__active_grid.n_y * self.__active_grid.grid_size_mm.y / grid_size_mm_y
|
||||
self._active_grid.n_y = round(
|
||||
self._active_grid.n_y * self._active_grid.grid_size_mm.y / grid_size_mm_y
|
||||
)
|
||||
|
||||
self.__active_grid.grid_size_mm = Coordinate(x=grid_size_mm_x, y=grid_size_mm_y)
|
||||
self._active_grid.grid_size_mm = Coordinate(x=grid_size_mm_x, y=grid_size_mm_y)
|
||||
|
||||
self.grid_scan_size_changed.emit(
|
||||
self.__active_grid.n_x,
|
||||
self.__active_grid.n_y,
|
||||
self.__active_grid.grid_size_mm.x,
|
||||
self.__active_grid.grid_size_mm.y,
|
||||
self._active_grid.n_x,
|
||||
self._active_grid.n_y,
|
||||
self._active_grid.grid_size_mm.x,
|
||||
self._active_grid.grid_size_mm.y,
|
||||
)
|
||||
|
||||
@Slot()
|
||||
def run_grid_scan(self):
|
||||
ag = self.__active_grid
|
||||
ag = self._active_grid
|
||||
grid = RasterGridRequest(
|
||||
dtz=ag.dtz,
|
||||
transmission=ag.transmission,
|
||||
@@ -476,7 +474,7 @@ class RasterGridManager(QObject):
|
||||
|
||||
@Slot()
|
||||
def run_grid_scan_auto(self):
|
||||
ag = self.__active_grid
|
||||
ag = self._active_grid
|
||||
grid = RasterGridRequest(
|
||||
dtz=ag.dtz,
|
||||
transmission=ag.transmission,
|
||||
@@ -506,10 +504,10 @@ class RasterGridManager(QObject):
|
||||
if visible_rect.isEmpty():
|
||||
visible_rect = QRectF()
|
||||
|
||||
self._draw_grid(painter, self.__active_grid, None, alpha, visible_rect)
|
||||
self._draw_grid(painter, self._active_grid, None, alpha, visible_rect)
|
||||
|
||||
for i in self.__completed_grids:
|
||||
match self.__metric:
|
||||
for i in self._completed_grids:
|
||||
match self._metric:
|
||||
case RasterGridMetric.SPOTS_LOW_RES:
|
||||
v = [obj.spots_low_res for obj in i.result.images]
|
||||
case RasterGridMetric.SPOTS:
|
||||
@@ -536,12 +534,12 @@ class RasterGridManager(QObject):
|
||||
v = [self.spot_ice_ratio(obj) for obj in i.result.images]
|
||||
case RasterGridMetric.SPOTS_INDEXED:
|
||||
v = [obj.spots_indexed for obj in i.result.images]
|
||||
self._draw_completed_heatmap(painter, i, v, alpha, visible_rect, (id(i), self.__metric))
|
||||
self._draw_completed_heatmap(painter, i, v, alpha, visible_rect, (id(i), self._metric))
|
||||
|
||||
def _invalidate_heatmap_cache(self) -> None:
|
||||
"""Drop all cached heatmap bitmaps. Call whenever the completed-grid data
|
||||
set or the active metric changes; cheap to rebuild lazily on next paint."""
|
||||
self.__heatmap_cache.clear()
|
||||
self._heatmap_cache.clear()
|
||||
|
||||
def _heatmap_image(
|
||||
self, cache_key: tuple, grid: RasterGridRequest, values: List[float] | List[int]
|
||||
@@ -550,7 +548,7 @@ class RasterGridManager(QObject):
|
||||
on a cache miss. One pixel per cell; colours baked at full opacity with
|
||||
the alpha channel encoding validity (overlay transparency is applied at
|
||||
blit time via painter opacity, so it never invalidates the cache)."""
|
||||
cached = self.__heatmap_cache.get(cache_key)
|
||||
cached = self._heatmap_cache.get(cache_key)
|
||||
if cached is not None:
|
||||
return cached[0]
|
||||
|
||||
@@ -558,7 +556,7 @@ class RasterGridManager(QObject):
|
||||
if built is None:
|
||||
return None
|
||||
|
||||
self.__heatmap_cache[cache_key] = built
|
||||
self._heatmap_cache[cache_key] = built
|
||||
return built[0]
|
||||
|
||||
def _build_heatmap_image(
|
||||
@@ -875,28 +873,28 @@ class RasterGridManager(QObject):
|
||||
|
||||
@Slot(float)
|
||||
def update_exposure_time(self, exp_time_s: float):
|
||||
self.__active_grid.exp_time_s = exp_time_s
|
||||
self._active_grid.exp_time_s = exp_time_s
|
||||
|
||||
@Slot(float)
|
||||
def update_transmission(self, transmission: float):
|
||||
self.__active_grid.transmission = transmission
|
||||
self._active_grid.transmission = transmission
|
||||
|
||||
@Slot(float)
|
||||
def update_dtz(self, dtz: float):
|
||||
self.__active_grid.dtz = dtz
|
||||
self._active_grid.dtz = dtz
|
||||
|
||||
@Slot(str)
|
||||
def update_filename(self, filename: str):
|
||||
self.__active_grid.file_prefix = filename
|
||||
self._active_grid.file_prefix = filename
|
||||
|
||||
@Slot(RasterGridMetric)
|
||||
def metric(self, r: RasterGridMetric):
|
||||
self.__metric = r
|
||||
self._metric = r
|
||||
self._invalidate_heatmap_cache()
|
||||
|
||||
@Slot()
|
||||
def clear_completed_grids(self):
|
||||
self.__completed_grids.clear()
|
||||
self._completed_grids.clear()
|
||||
self._invalidate_heatmap_cache()
|
||||
self.completed_grid_updated.emit()
|
||||
|
||||
@@ -904,50 +902,50 @@ class RasterGridManager(QObject):
|
||||
def grid_scan_completed(self, raster_list: CompletedRasterGrid):
|
||||
last_raster = None
|
||||
for raster in raster_list.r:
|
||||
self.__completed_grids.append(raster)
|
||||
self._completed_grids.append(raster)
|
||||
last_raster = raster
|
||||
self.__active_grid.n_x = 0
|
||||
self.__active_grid.n_y = 0
|
||||
self._active_grid.n_x = 0
|
||||
self._active_grid.n_y = 0
|
||||
self._invalidate_heatmap_cache()
|
||||
if last_raster is not None and last_raster.result.file_prefix is not None:
|
||||
com = last_raster.centre_of_mass
|
||||
logger.info(f"COM: {com}")
|
||||
cell = com.max_image if com else 0
|
||||
self.image_selected.emit(self.__detector_url, cell)
|
||||
self.image_selected.emit(self._detector_url, cell)
|
||||
self.completed_grid_updated.emit()
|
||||
|
||||
@Slot(int, bool)
|
||||
def completed_grid_scan_toggle_visible(self, row: int, visible: bool):
|
||||
if 0 <= row < len(self.__completed_grids):
|
||||
self.__completed_grids[row].request.visible = visible
|
||||
if 0 <= row < len(self._completed_grids):
|
||||
self._completed_grids[row].request.visible = visible
|
||||
self.completed_grid_updated.emit()
|
||||
|
||||
@Slot(int)
|
||||
def completed_grid_scan_delete(self, row: int):
|
||||
if 0 <= row < len(self.__completed_grids):
|
||||
del self.__completed_grids[row]
|
||||
if 0 <= row < len(self._completed_grids):
|
||||
del self._completed_grids[row]
|
||||
self._invalidate_heatmap_cache()
|
||||
self.completed_grid_updated.emit()
|
||||
|
||||
@Slot(int)
|
||||
def completed_grid_scan_goto(self, row: int):
|
||||
if 0 <= row < len(self.__completed_grids):
|
||||
self.omega.emit(self.__completed_grids[row].request.omega_deg)
|
||||
if 0 <= row < len(self._completed_grids):
|
||||
self.omega.emit(self._completed_grids[row].request.omega_deg)
|
||||
self.smargon.emit(
|
||||
SmargonCoordinate(
|
||||
phi_deg=self.__completed_grids[row].request.smargon_top_left.phi_deg,
|
||||
chi_deg=self.__completed_grids[row].request.smargon_top_left.chi_deg,
|
||||
phi_deg=self._completed_grids[row].request.smargon_top_left.phi_deg,
|
||||
chi_deg=self._completed_grids[row].request.smargon_top_left.chi_deg,
|
||||
)
|
||||
)
|
||||
|
||||
@Slot(int)
|
||||
def completed_grid_redo(self, row: int):
|
||||
if 0 <= row < len(self.__completed_grids):
|
||||
src = self.__completed_grids[row].request
|
||||
self.__active_grid.n_x = src.n_x
|
||||
self.__active_grid.n_y = src.n_y
|
||||
self.__active_grid.grid_size_mm = Coordinate(x=src.grid_size_mm.x, y=src.grid_size_mm.y)
|
||||
self.__active_grid.smargon_top_left = SmargonCoordinate(
|
||||
if 0 <= row < len(self._completed_grids):
|
||||
src = self._completed_grids[row].request
|
||||
self._active_grid.n_x = src.n_x
|
||||
self._active_grid.n_y = src.n_y
|
||||
self._active_grid.grid_size_mm = Coordinate(x=src.grid_size_mm.x, y=src.grid_size_mm.y)
|
||||
self._active_grid.smargon_top_left = SmargonCoordinate(
|
||||
sh_mm=Coordinate(
|
||||
x=src.smargon_top_left.sh_mm.x,
|
||||
y=src.smargon_top_left.sh_mm.y,
|
||||
@@ -956,18 +954,18 @@ class RasterGridManager(QObject):
|
||||
phi_deg=src.smargon_top_left.phi_deg,
|
||||
chi_deg=src.smargon_top_left.chi_deg,
|
||||
)
|
||||
self.__active_grid.omega_deg = src.omega_deg
|
||||
self._active_grid.omega_deg = src.omega_deg
|
||||
|
||||
self.__active_grid.visible = True
|
||||
self.__completed_grids[row].request.visible = False
|
||||
self._active_grid.visible = True
|
||||
self._completed_grids[row].request.visible = False
|
||||
|
||||
self.grid_scan_size_changed.emit(
|
||||
self.__active_grid.n_x,
|
||||
self.__active_grid.n_y,
|
||||
self.__active_grid.grid_size_mm.x,
|
||||
self.__active_grid.grid_size_mm.y,
|
||||
self._active_grid.n_x,
|
||||
self._active_grid.n_y,
|
||||
self._active_grid.grid_size_mm.x,
|
||||
self._active_grid.grid_size_mm.y,
|
||||
)
|
||||
self.completed_grid_updated.emit()
|
||||
|
||||
def get_completed_grids(self) -> List[CompletedRasterGridElem]:
|
||||
return self.__completed_grids
|
||||
return self._completed_grids
|
||||
|
||||
@@ -8,10 +8,10 @@ class RotationScanManager(QObject):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.__beamline = mx_beamline()
|
||||
self.__detector_url = get_jfjoch_url(self.__beamline)
|
||||
self._beamline = mx_beamline()
|
||||
self._detector_url = get_jfjoch_url(self._beamline)
|
||||
|
||||
@Slot(CompletedRotationScan)
|
||||
def scan_completed(self, r: CompletedRotationScan):
|
||||
if r.result.file_prefix is not None:
|
||||
self.file_ready.emit(self.__detector_url, 0)
|
||||
self.file_ready.emit(self._detector_url, 0)
|
||||
|
||||
@@ -7,11 +7,11 @@ class SampleMountLogic(QObject):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.__sample = None
|
||||
self._sample = None
|
||||
|
||||
@Slot(DAQStatusModel)
|
||||
def update_daq_status(self, s: DAQStatusModel):
|
||||
if s.sample != self.__sample:
|
||||
if s.sample != self._sample:
|
||||
self.sample_changed.emit(s.sample)
|
||||
|
||||
self.__sample = s.sample
|
||||
self._sample = s.sample
|
||||
|
||||
@@ -21,68 +21,68 @@ class SampleCameraThread(QThread):
|
||||
def __init__(self, zmq_url: str, parent=None):
|
||||
super().__init__(parent)
|
||||
context = zmq.Context()
|
||||
self.__socket = context.socket(zmq.SUB)
|
||||
self.__socket.setsockopt(zmq.SUBSCRIBE, b"")
|
||||
self.__socket.setsockopt(zmq.RCVTIMEO, 500)
|
||||
self.__socket.connect(zmq_url)
|
||||
self._socket = context.socket(zmq.SUB)
|
||||
self._socket.setsockopt(zmq.SUBSCRIBE, b"")
|
||||
self._socket.setsockopt(zmq.RCVTIMEO, 500)
|
||||
self._socket.connect(zmq_url)
|
||||
|
||||
self.running = True
|
||||
self.__measure_focus = False
|
||||
self.__focus_mask = None
|
||||
self.__beam_x = 0
|
||||
self.__beam_y = 0
|
||||
self.__last_beam_pos = None
|
||||
self.__radius = 40
|
||||
self._measure_focus = False
|
||||
self._focus_mask = None
|
||||
self._beam_x = 0
|
||||
self._beam_y = 0
|
||||
self._last_beam_pos = None
|
||||
self._radius = 40
|
||||
|
||||
self.__fps_window_start = time.perf_counter()
|
||||
self.__fps_frame_count = 0
|
||||
self.__fps_emit_period_s = 0.5
|
||||
self.__last_frame_time = None
|
||||
self.__no_frame_timeout_s = 5.0
|
||||
self._fps_window_start = time.perf_counter()
|
||||
self._fps_frame_count = 0
|
||||
self._fps_emit_period_s = 0.5
|
||||
self._last_frame_time = None
|
||||
self._no_frame_timeout_s = 5.0
|
||||
|
||||
self.__camera_available = False
|
||||
self.__last_camera_error: str | None = None
|
||||
self._camera_available = False
|
||||
self._last_camera_error: str | None = None
|
||||
|
||||
def __set_camera_available(self, available: bool, error: str | None = None) -> None:
|
||||
if available != self.__camera_available:
|
||||
self.__camera_available = available
|
||||
if available != self._camera_available:
|
||||
self._camera_available = available
|
||||
self.camera_availability_changed.emit(available)
|
||||
|
||||
if error is not None and error != self.__last_camera_error:
|
||||
self.__last_camera_error = error
|
||||
if error is not None and error != self._last_camera_error:
|
||||
self._last_camera_error = error
|
||||
self.camera_error.emit(error)
|
||||
|
||||
if available:
|
||||
self.__last_camera_error = None
|
||||
self._last_camera_error = None
|
||||
|
||||
@Slot(DAQStatusModel)
|
||||
def update_daq_status(self, s: DAQStatusModel):
|
||||
self.__beam_x = s.geom.beam_location_pxl.x
|
||||
self.__beam_y = s.geom.beam_location_pxl.y
|
||||
if (self.__beam_x, self.__beam_y) != self.__last_beam_pos:
|
||||
self.__focus_mask = None # Invalidate cache
|
||||
self.__last_beam_pos = (self.__beam_x, self.__beam_y)
|
||||
self._beam_x = s.geom.beam_location_pxl.x
|
||||
self._beam_y = s.geom.beam_location_pxl.y
|
||||
if (self._beam_x, self._beam_y) != self._last_beam_pos:
|
||||
self._focus_mask = None # Invalidate cache
|
||||
self._last_beam_pos = (self._beam_x, self._beam_y)
|
||||
|
||||
@Slot(bool)
|
||||
def enable_focus_measurement(self, enabled: bool = True):
|
||||
"""Enable or disable focus measurement."""
|
||||
self.__measure_focus = enabled
|
||||
self._measure_focus = enabled
|
||||
|
||||
def run(self):
|
||||
while self.running:
|
||||
try:
|
||||
r = self.__socket.recv_multipart()
|
||||
r = self._socket.recv_multipart()
|
||||
|
||||
now = time.perf_counter()
|
||||
self.__last_frame_time = now
|
||||
self.__fps_frame_count += 1
|
||||
self._last_frame_time = now
|
||||
self._fps_frame_count += 1
|
||||
|
||||
elapsed = now - self.__fps_window_start
|
||||
if elapsed >= self.__fps_emit_period_s:
|
||||
fps = self.__fps_frame_count / elapsed if elapsed > 0 else 0.0
|
||||
elapsed = now - self._fps_window_start
|
||||
if elapsed >= self._fps_emit_period_s:
|
||||
fps = self._fps_frame_count / elapsed if elapsed > 0 else 0.0
|
||||
self.fps_measure.emit(float(fps))
|
||||
self.__fps_window_start = now
|
||||
self.__fps_frame_count = 0
|
||||
self._fps_window_start = now
|
||||
self._fps_frame_count = 0
|
||||
|
||||
if len(r) < 2:
|
||||
continue
|
||||
@@ -106,7 +106,7 @@ class SampleCameraThread(QThread):
|
||||
encoded = np.frombuffer(data, dtype=np.uint8)
|
||||
bgr = cv2.imdecode(encoded, cv2.IMREAD_COLOR)
|
||||
if bgr is None:
|
||||
self.__set_camera_available(
|
||||
self._set_camera_available(
|
||||
False, "Sample camera feed unavailable: failed to decode JPEG frame"
|
||||
)
|
||||
continue
|
||||
@@ -116,24 +116,24 @@ class SampleCameraThread(QThread):
|
||||
raw = np.frombuffer(data, np.uint8).reshape((h, w))
|
||||
rgb = cv2.cvtColor(raw, cv2.COLOR_BAYER_GB2RGB)
|
||||
else:
|
||||
self.__set_camera_available(
|
||||
self._set_camera_available(
|
||||
False,
|
||||
f"Sample camera feed unavailable: unsupported frame header {header}",
|
||||
)
|
||||
continue
|
||||
|
||||
self.__set_camera_available(True)
|
||||
self._set_camera_available(True)
|
||||
|
||||
if self.__measure_focus:
|
||||
if self._measure_focus:
|
||||
gray = cv2.cvtColor(rgb, cv2.COLOR_RGB2GRAY)
|
||||
if self.__focus_mask is None or self.__focus_mask.shape != gray.shape:
|
||||
if self._focus_mask is None or self._focus_mask.shape != gray.shape:
|
||||
height, width = gray.shape
|
||||
y, x = np.ogrid[:height, :width]
|
||||
self.__focus_mask = (x - self.__beam_x) ** 2 + (
|
||||
y - self.__beam_y
|
||||
) ** 2 <= self.__radius**2
|
||||
self._focus_mask = (x - self._beam_x) ** 2 + (
|
||||
y - self._beam_y
|
||||
) ** 2 <= self._radius**2
|
||||
|
||||
sharpness = focus_measure_edges(gray, self.__focus_mask)
|
||||
sharpness = focus_measure_edges(gray, self._focus_mask)
|
||||
self.focus_measure.emit(sharpness)
|
||||
|
||||
qimage = QImage(
|
||||
@@ -141,33 +141,33 @@ class SampleCameraThread(QThread):
|
||||
).copy()
|
||||
self.camera_image.emit(QPixmap.fromImage(qimage))
|
||||
else:
|
||||
self.__set_camera_available(
|
||||
self._set_camera_available(
|
||||
False, "Sample camera feed unavailable: no frame header in zmq stream"
|
||||
)
|
||||
except zmq.Again: # Timeout occurred
|
||||
now = time.perf_counter()
|
||||
elapsed = now - self.__fps_window_start
|
||||
if elapsed >= self.__fps_emit_period_s:
|
||||
elapsed = now - self._fps_window_start
|
||||
if elapsed >= self._fps_emit_period_s:
|
||||
no_frames_long = (
|
||||
self.__last_frame_time is None
|
||||
or (now - self.__last_frame_time) >= self.__no_frame_timeout_s
|
||||
self._last_frame_time is None
|
||||
or (now - self._last_frame_time) >= self._no_frame_timeout_s
|
||||
)
|
||||
self.fps_measure.emit(float("nan") if no_frames_long else 0.0)
|
||||
self.__fps_window_start = now
|
||||
self.__fps_frame_count = 0
|
||||
self._fps_window_start = now
|
||||
self._fps_frame_count = 0
|
||||
|
||||
if no_frames_long:
|
||||
self.__set_camera_available(False, "Sample camera feed unavailable")
|
||||
self._set_camera_available(False, "Sample camera feed unavailable")
|
||||
continue # Check self.running again
|
||||
except Exception as e:
|
||||
self.__set_camera_available(False, f"Sample camera feed unavailable: {e}")
|
||||
self._set_camera_available(False, f"Sample camera feed unavailable: {e}")
|
||||
self.running = False
|
||||
|
||||
def stop(self):
|
||||
self.running = False
|
||||
|
||||
if self.__socket:
|
||||
self.__socket.close()
|
||||
if self._socket:
|
||||
self._socket.close()
|
||||
|
||||
if self.isRunning():
|
||||
self.quit()
|
||||
|
||||
+191
-191
@@ -142,15 +142,15 @@ class DAQWorker(QObject):
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self._active_status_error_key = None
|
||||
self.__token = token
|
||||
self.__base_url = base_url
|
||||
self.__net_manager = QNetworkAccessManager()
|
||||
self.__net_manager.sslErrors.connect(self._handle_ssl_errors)
|
||||
self.__timer = QTimer()
|
||||
self.__timer.setInterval(500)
|
||||
self.__timer.timeout.connect(self.regular_update)
|
||||
self.__timer.start()
|
||||
self.__counter = 0
|
||||
self._token = token
|
||||
self._base_url = base_url
|
||||
self._net_manager = QNetworkAccessManager()
|
||||
self._net_manager.sslErrors.connect(self._handle_ssl_errors)
|
||||
self._timer = QTimer()
|
||||
self._timer.setInterval(500)
|
||||
self._timer.timeout.connect(self.regular_update)
|
||||
self._timer.start()
|
||||
self._counter = 0
|
||||
self._automation_progress_buffer = ""
|
||||
self._cleanup_done = False
|
||||
|
||||
@@ -215,7 +215,7 @@ class DAQWorker(QObject):
|
||||
self._local_contact_metadata_poll_enabled = True
|
||||
self._local_contact_metadata_error: str | None = None
|
||||
|
||||
if self.__base_url is not None:
|
||||
if self._base_url is not None:
|
||||
self.start_face_detection_stream()
|
||||
self.start_baton_stream()
|
||||
self.start_automation_progress_stream()
|
||||
@@ -333,17 +333,17 @@ class DAQWorker(QObject):
|
||||
"""
|
||||
Periodically triggered slot to update spreadsheet and request status from server.
|
||||
"""
|
||||
if self.__counter % SPREADHSEET_FREQUENCY == 0:
|
||||
if self._counter % SPREADHSEET_FREQUENCY == 0:
|
||||
self.load_spreadsheet()
|
||||
self.load_reference_tools()
|
||||
self.__counter = (self.__counter + 1) % SPREADHSEET_FREQUENCY
|
||||
self._counter = (self._counter + 1) % SPREADHSEET_FREQUENCY
|
||||
self.send_status_request()
|
||||
|
||||
def send_status_request(self):
|
||||
"""
|
||||
Send an asynchronous HTTP GET request to the /status endpoint.
|
||||
"""
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
return
|
||||
|
||||
now = time.monotonic()
|
||||
@@ -351,16 +351,16 @@ class DAQWorker(QObject):
|
||||
return
|
||||
self._last_status_request_ts = now
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/status"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/status"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(request)
|
||||
reply.finished.connect(lambda: self.handle_status_response(reply))
|
||||
|
||||
def server_about_info(self) -> tuple[str, str]:
|
||||
version_request = QNetworkRequest(QUrl(f"{self.__base_url}/about/running_version"))
|
||||
version_reply = self.__net_manager.get(version_request)
|
||||
file_request = QNetworkRequest(QUrl(f"{self.__base_url}/about/server_file_path"))
|
||||
file_reply = self.__net_manager.get(file_request)
|
||||
version_request = QNetworkRequest(QUrl(f"{self._base_url}/about/running_version"))
|
||||
version_reply = self._net_manager.get(version_request)
|
||||
file_request = QNetworkRequest(QUrl(f"{self._base_url}/about/server_file_path"))
|
||||
file_reply = self._net_manager.get(file_request)
|
||||
version = (
|
||||
str(version_reply.readAll()) if version_reply.waitForReadyRead(500) else "Not connected"
|
||||
)
|
||||
@@ -734,15 +734,15 @@ class DAQWorker(QObject):
|
||||
url: The relative URL (endpoint).
|
||||
body: The request body string.
|
||||
"""
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info(f"POST /{url}: {body}")
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/{url}"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/{url}"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
if str:
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
reply = self.__net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply = self._net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply.finished.connect(lambda: self.handle_req_response(reply))
|
||||
|
||||
def generic_put(self, url: str, body: str = ""):
|
||||
@@ -753,14 +753,14 @@ class DAQWorker(QObject):
|
||||
url: The relative URL (endpoint).
|
||||
body: The request body string.
|
||||
"""
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info(f"PUT /{url}: {body}")
|
||||
return
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/{url}"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/{url}"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
if str:
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
reply = self.__net_manager.put(request, QByteArray(body.encode("utf-8")))
|
||||
reply = self._net_manager.put(request, QByteArray(body.encode("utf-8")))
|
||||
reply.finished.connect(lambda: self.handle_req_response(reply))
|
||||
|
||||
def generic_delete(self, url: str):
|
||||
@@ -770,13 +770,13 @@ class DAQWorker(QObject):
|
||||
Args:
|
||||
url: The relative URL (endpoint).
|
||||
"""
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info(f"DELETE /{url}")
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/{url}"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.deleteResource(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/{url}"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.deleteResource(request)
|
||||
reply.finished.connect(lambda: self.handle_req_response(reply))
|
||||
|
||||
@Slot(float)
|
||||
@@ -869,45 +869,45 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot(str)
|
||||
def free_beamline(self, confirmation_code: str):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info("POST /state/free_beamline")
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/state/free_beamline"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/state/free_beamline"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
body = json.dumps({"confirmation_code": confirmation_code})
|
||||
reply = self.__net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply = self._net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply.finished.connect(
|
||||
lambda: self._handle_recovery_action_response(reply, "Beamline busy flag cleared.")
|
||||
)
|
||||
|
||||
@Slot(str)
|
||||
def take_over_beamline(self, confirmation_code: str):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info("POST /access/take_over_beamline")
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/access/take_over_beamline"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/access/take_over_beamline"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
body = json.dumps({"confirmation_code": confirmation_code})
|
||||
reply = self.__net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply = self._net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply.finished.connect(
|
||||
lambda: self._handle_recovery_action_response(reply, "Beamline session taken over.")
|
||||
)
|
||||
|
||||
@Slot(str)
|
||||
def recover_beamline(self, confirmation_code: str):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info("POST /recovery/recover_beamline")
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/recovery/recover_beamline"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/recovery/recover_beamline"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
body = json.dumps({"confirmation_code": confirmation_code})
|
||||
reply = self.__net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply = self._net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply.finished.connect(
|
||||
lambda: self._handle_recovery_action_response(
|
||||
reply, "Beamline recovered to Maintenance."
|
||||
@@ -916,15 +916,15 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot(str)
|
||||
def recovery_unmount_sample(self, confirmation_code: str):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info("POST /recovery/unmount_sample")
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/recovery/unmount_sample"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/recovery/unmount_sample"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
body = json.dumps({"confirmation_code": confirmation_code})
|
||||
reply = self.__net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply = self._net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply.finished.connect(
|
||||
lambda: self._handle_recovery_action_response(reply, "Recovery unmount completed.")
|
||||
)
|
||||
@@ -960,13 +960,13 @@ class DAQWorker(QObject):
|
||||
@Slot()
|
||||
def get_all_pgroups(self):
|
||||
logger.debug("PUT /access/all_pgroups")
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
self.staff_pgroups_loaded.emit([])
|
||||
return
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/access/all_pgroups"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/access/all_pgroups"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
reply = self.__net_manager.put(request, QByteArray(b""))
|
||||
reply = self._net_manager.put(request, QByteArray(b""))
|
||||
reply.finished.connect(lambda: self._handle_all_pgroups_response(reply))
|
||||
|
||||
@Slot(SampleCameraSettings)
|
||||
@@ -1032,11 +1032,11 @@ class DAQWorker(QObject):
|
||||
"""
|
||||
self.run_number_incremented.emit()
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/scan/rotation"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/scan/rotation"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
body = r.model_dump_json()
|
||||
reply = self.__net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply = self._net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply.finished.connect(lambda: self.handle_rotation_scan_response(reply))
|
||||
|
||||
def handle_raster_scan_response(self, reply: QNetworkReply):
|
||||
@@ -1086,7 +1086,7 @@ class DAQWorker(QObject):
|
||||
r: The RasterGridRequest object.
|
||||
"""
|
||||
self.run_number_incremented.emit()
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info(f"POST /scan/raster: {r.model_dump_json()}")
|
||||
image_number = r.get_image_number()
|
||||
new_copy = copy.deepcopy(r)
|
||||
@@ -1113,11 +1113,11 @@ class DAQWorker(QObject):
|
||||
self.raster_scan_completed.emit(reply)
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/scan/raster?auto_center=false"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/scan/raster?auto_center=false"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
body = r.model_dump_json()
|
||||
reply = self.__net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply = self._net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply.finished.connect(lambda: self.handle_raster_scan_response(reply))
|
||||
|
||||
@Slot(RasterGridRequest)
|
||||
@@ -1129,7 +1129,7 @@ class DAQWorker(QObject):
|
||||
r: The RasterGridRequest object.
|
||||
"""
|
||||
self.run_number_incremented.emit()
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info(f"POST /scan/raster: {r.model_dump_json()}")
|
||||
image_number = r.get_image_number()
|
||||
new_copy = copy.deepcopy(r)
|
||||
@@ -1155,32 +1155,32 @@ class DAQWorker(QObject):
|
||||
self.raster_scan_completed.emit(reply)
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/scan/raster?auto_center=true"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/scan/raster?auto_center=true"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
body = r.model_dump_json()
|
||||
reply = self.__net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply = self._net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply.finished.connect(lambda: self.handle_raster_scan_response(reply))
|
||||
|
||||
@Slot()
|
||||
def load_spreadsheet(self):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info("GET /sample/spreadsheet")
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/sample/spreadsheet"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/sample/spreadsheet"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(request)
|
||||
reply.finished.connect(lambda: self.handle_spreadsheet_response(reply))
|
||||
|
||||
@Slot()
|
||||
def load_reference_tools(self):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info("GET /sample/reference_tools")
|
||||
return
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/sample/reference_tools"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/sample/reference_tools"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(request)
|
||||
reply.finished.connect(lambda: self.handle_reference_tools_response(reply))
|
||||
|
||||
def _emit_manual_collection_critical_failure(self, msg: str) -> None:
|
||||
@@ -1372,20 +1372,20 @@ class DAQWorker(QObject):
|
||||
Args:
|
||||
s: The SampleShortInfo object.
|
||||
"""
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info(f"POST /scan/auto: {s.model_dump_json()}")
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/scan/auto"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/scan/auto"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
body = s.model_dump_json()
|
||||
reply = self.__net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply = self._net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply.finished.connect(lambda: self.handle_auto_scan_response(reply, s.db_id))
|
||||
|
||||
@Slot(SimpleScanParameters)
|
||||
def smart_params(self, p: SimpleScanParameters):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info(f"POST /scan/smart_params: {p.model_dump_json()}")
|
||||
return
|
||||
self.generic_post("scan/smart_params", p.model_dump_json())
|
||||
@@ -1420,26 +1420,26 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot()
|
||||
def resync_sample(self):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info("POST /sample/resync")
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/sample/resync"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/sample/resync"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
reply = self.__net_manager.post(request, QByteArray(b""))
|
||||
reply = self._net_manager.post(request, QByteArray(b""))
|
||||
reply.finished.connect(lambda: self._handle_sample_resync_response(reply))
|
||||
|
||||
@Slot()
|
||||
def resync_local_contact_detector_metadata(self):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info("POST /local_contact/resync/detector_metadata")
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/local_contact/resync/detector_metadata"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/local_contact/resync/detector_metadata"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
reply = self.__net_manager.post(request, QByteArray(b""))
|
||||
reply = self._net_manager.post(request, QByteArray(b""))
|
||||
reply.finished.connect(lambda: self._handle_detector_metadata_resync_response(reply))
|
||||
|
||||
@Slot(float)
|
||||
@@ -1486,7 +1486,7 @@ class DAQWorker(QObject):
|
||||
if not self._local_contact_metadata_poll_enabled:
|
||||
return
|
||||
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
self.local_contact_simulation_state_loaded.emit(
|
||||
{
|
||||
"bec": False,
|
||||
@@ -1498,9 +1498,9 @@ class DAQWorker(QObject):
|
||||
)
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/local_contact/simulation_state"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/local_contact/simulation_state"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(request)
|
||||
reply.finished.connect(lambda: self._handle_local_contact_simulation_state_response(reply))
|
||||
|
||||
def _handle_local_contact_simulation_state_response(self, reply: QNetworkReply):
|
||||
@@ -1523,13 +1523,13 @@ class DAQWorker(QObject):
|
||||
if not self._local_contact_metadata_poll_enabled:
|
||||
return
|
||||
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
self.local_contact_device_state_loaded.emit({})
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/local_contact/device_state"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/local_contact/device_state"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(request)
|
||||
reply.finished.connect(lambda: self._handle_local_contact_device_state_response(reply))
|
||||
|
||||
def _handle_local_contact_device_state_response(self, reply: QNetworkReply):
|
||||
@@ -1552,13 +1552,13 @@ class DAQWorker(QObject):
|
||||
if not self._local_contact_metadata_poll_enabled:
|
||||
return
|
||||
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
self.local_contact_links_loaded.emit({})
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/local_contact/links"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/local_contact/links"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(request)
|
||||
reply.finished.connect(lambda: self._handle_local_contact_links_response(reply))
|
||||
|
||||
def _handle_local_contact_links_response(self, reply: QNetworkReply):
|
||||
@@ -1578,7 +1578,7 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot()
|
||||
def load_local_contact_config(self):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
self.local_contact_config_loaded.emit(
|
||||
{
|
||||
"mount_to_center_sleep_s": 0.0,
|
||||
@@ -1588,9 +1588,9 @@ class DAQWorker(QObject):
|
||||
)
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/local_contact/config"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/local_contact/config"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(request)
|
||||
reply.finished.connect(lambda: self._handle_local_contact_config_response(reply))
|
||||
|
||||
def _handle_local_contact_config_response(self, reply: QNetworkReply):
|
||||
@@ -1610,15 +1610,15 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot(dict)
|
||||
def set_local_contact_config(self, payload: dict):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
self.local_contact_config_saved.emit(payload)
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/local_contact/config"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/local_contact/config"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
body = QByteArray(json.dumps(payload).encode("utf-8"))
|
||||
reply = self.__net_manager.put(request, body)
|
||||
reply = self._net_manager.put(request, body)
|
||||
reply.finished.connect(lambda: self._handle_set_local_contact_config_response(reply))
|
||||
|
||||
def _handle_set_local_contact_config_response(self, reply: QNetworkReply):
|
||||
@@ -1651,14 +1651,14 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot()
|
||||
def bec_list_all_user_macros(self):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info("GET /bec/user_macros")
|
||||
self.bec_user_macros_loaded.emit([])
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/bec/user_macros"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/bec/user_macros"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(request)
|
||||
reply.finished.connect(lambda: self._handle_bec_user_macros_response(reply))
|
||||
|
||||
def _handle_bec_user_macros_response(self, reply: QNetworkReply):
|
||||
@@ -1674,14 +1674,14 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot()
|
||||
def bec_list_all_devices(self):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info("GET /bec/devices")
|
||||
self.bec_devices_loaded.emit([])
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/bec/devices"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/bec/devices"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(request)
|
||||
reply.finished.connect(lambda: self._handle_bec_devices_response(reply))
|
||||
|
||||
def _handle_bec_devices_response(self, reply: QNetworkReply):
|
||||
@@ -1763,13 +1763,13 @@ class DAQWorker(QObject):
|
||||
"""
|
||||
Request an ML-based bounding box for the current sample.
|
||||
"""
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info("POST /alc/ml_bounding_box")
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/alc/ml_bounding_box"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.post(request, QByteArray(b""))
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/alc/ml_bounding_box"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.post(request, QByteArray(b""))
|
||||
reply.finished.connect(lambda: self.handle_ml_box_response(reply))
|
||||
|
||||
def _handle_face_detection_response(self, reply: QNetworkReply):
|
||||
@@ -1818,7 +1818,7 @@ class DAQWorker(QObject):
|
||||
)
|
||||
return
|
||||
|
||||
if self.__base_url is not None:
|
||||
if self._base_url is not None:
|
||||
QTimer.singleShot(1000, self.start_face_detection_stream)
|
||||
|
||||
@staticmethod
|
||||
@@ -1990,11 +1990,11 @@ class DAQWorker(QObject):
|
||||
)
|
||||
return
|
||||
|
||||
if self.__base_url is not None:
|
||||
if self._base_url is not None:
|
||||
QTimer.singleShot(1000, self.start_automation_progress_stream)
|
||||
|
||||
def start_automation_progress_stream(self):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
return
|
||||
|
||||
if self._automation_progress_stream_reply is not None:
|
||||
@@ -2005,15 +2005,15 @@ class DAQWorker(QObject):
|
||||
|
||||
self._automation_progress_buffer = ""
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/sse/automation_progress"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/sse/automation_progress"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(request)
|
||||
reply.readyRead.connect(lambda: self._read_automation_progress_stream(reply))
|
||||
reply.finished.connect(self._restart_automation_progress_stream)
|
||||
self._automation_progress_stream_reply = reply
|
||||
|
||||
def start_face_detection_stream(self):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
return
|
||||
|
||||
if self._face_detection_stream_reply is not None:
|
||||
@@ -2022,9 +2022,9 @@ class DAQWorker(QObject):
|
||||
if self._face_detection_stream_blocked_403:
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/sse/face_detection"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/sse/face_detection"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(request)
|
||||
reply.readyRead.connect(lambda: self._read_face_detection_stream(reply))
|
||||
reply.finished.connect(self._restart_face_detection_stream)
|
||||
self._face_detection_stream_reply = reply
|
||||
@@ -2038,16 +2038,16 @@ class DAQWorker(QObject):
|
||||
steps: Number of rotation steps.
|
||||
step_size: Degrees per step.
|
||||
"""
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info(f"POST /face_detection/run?steps={steps}&step_size={step_size}")
|
||||
return
|
||||
|
||||
request = QNetworkRequest(
|
||||
QUrl(f"{self.__base_url}/face_detection/run?steps={steps}&step_size={step_size}")
|
||||
QUrl(f"{self._base_url}/face_detection/run?steps={steps}&step_size={step_size}")
|
||||
)
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
reply = self.__net_manager.post(request, QByteArray(b""))
|
||||
reply = self._net_manager.post(request, QByteArray(b""))
|
||||
reply.finished.connect(lambda: self._handle_face_detection_response(reply))
|
||||
|
||||
@Slot()
|
||||
@@ -2070,9 +2070,9 @@ class DAQWorker(QObject):
|
||||
data = json.loads(response_data) if response_data else []
|
||||
if emit_status:
|
||||
# fetch status and bkg in parallel (simple sequential here)
|
||||
status_req = QNetworkRequest(QUrl(f"{self.__base_url}/fluorimeter/status"))
|
||||
status_req.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
status_reply = self.__net_manager.get(status_req)
|
||||
status_req = QNetworkRequest(QUrl(f"{self._base_url}/fluorimeter/status"))
|
||||
status_req.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
status_reply = self._net_manager.get(status_req)
|
||||
status_reply.finished.connect(
|
||||
lambda: self._handle_fluorimeter_status_and_emit(data, status_reply)
|
||||
)
|
||||
@@ -2086,9 +2086,9 @@ class DAQWorker(QObject):
|
||||
try:
|
||||
s_payload = self.handle_response(status_reply)
|
||||
s = int(s_payload) if s_payload not in ("", "null") else -1
|
||||
b_req = QNetworkRequest(QUrl(f"{self.__base_url}/fluorimeter/background"))
|
||||
b_req.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
b_reply = self.__net_manager.get(b_req)
|
||||
b_req = QNetworkRequest(QUrl(f"{self._base_url}/fluorimeter/background"))
|
||||
b_req.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
b_reply = self._net_manager.get(b_req)
|
||||
b_reply.finished.connect(lambda: self._emit_fluorimeter_with_bkg(data, s, b_reply))
|
||||
except Exception as e:
|
||||
logger.error(f"Fluorimeter status error: {e}")
|
||||
@@ -2107,13 +2107,13 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot()
|
||||
def fluorimeter_spectrum(self, f: FluorescenceSpectrumParameterModel):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
return
|
||||
req = QNetworkRequest(QUrl(f"{self.__base_url}/fluorimeter/spectrum"))
|
||||
req.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
req = QNetworkRequest(QUrl(f"{self._base_url}/fluorimeter/spectrum"))
|
||||
req.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
req.setRawHeader(b"Content-Type", b"application/json")
|
||||
body = f.model_dump_json()
|
||||
reply = self.__net_manager.post(req, QByteArray(body.encode("utf-8")))
|
||||
reply = self._net_manager.post(req, QByteArray(body.encode("utf-8")))
|
||||
reply.finished.connect(lambda: self._handle_fluorimeter_spectrum(reply))
|
||||
|
||||
def _handle_fluorimeter_spectrum(self, reply: QNetworkReply):
|
||||
@@ -2127,20 +2127,20 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot()
|
||||
def fluorimeter_request_snapshot(self):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
return
|
||||
req = QNetworkRequest(QUrl(f"{self.__base_url}/fluorimeter/data"))
|
||||
req.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(req)
|
||||
req = QNetworkRequest(QUrl(f"{self._base_url}/fluorimeter/data"))
|
||||
req.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(req)
|
||||
reply.finished.connect(lambda: self._handle_fluorimeter_data(reply, emit_status=True))
|
||||
|
||||
# Optional: SSE listener for live updates
|
||||
def start_fluorimeter_stream(self):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
return
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/sse/fluorimeter"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/sse/fluorimeter"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(request)
|
||||
reply.readyRead.connect(lambda: self._read_fluorimeter_stream(reply))
|
||||
reply.finished.connect(lambda: reply.deleteLater())
|
||||
|
||||
@@ -2186,19 +2186,19 @@ class DAQWorker(QObject):
|
||||
|
||||
Server default is grouped. We flatten grouped payloads for existing UI.
|
||||
"""
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
self.error_codes_loaded.emit(export_error_codes())
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/meta/error-codes"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/meta/error-codes"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(request)
|
||||
reply.finished.connect(lambda: self._handle_error_codes_response(reply))
|
||||
|
||||
def _retry_error_codes_legacy(self) -> None:
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/meta/error-codes/flat"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/meta/error-codes/flat"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(request)
|
||||
reply.finished.connect(lambda: self._handle_error_codes_response(reply))
|
||||
|
||||
@Slot(QNetworkReply)
|
||||
@@ -2228,7 +2228,7 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot(str, str)
|
||||
def send_screenshot_db(self, filename: str = "", message: str = ""):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info(f"POST /samcam/send_screenshot_db?filename={filename}&message={message}")
|
||||
return
|
||||
|
||||
@@ -2248,22 +2248,22 @@ class DAQWorker(QObject):
|
||||
|
||||
def start_baton_stream(self):
|
||||
"""Start SSE stream for baton status updates."""
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
return
|
||||
|
||||
if self._baton_stream_reply is not None:
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/sse/baton"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/sse/baton"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(request)
|
||||
reply.readyRead.connect(lambda: self._read_baton_stream(reply))
|
||||
reply.finished.connect(self._restart_baton_stream)
|
||||
self._baton_stream_reply = reply
|
||||
|
||||
def _restart_baton_stream(self):
|
||||
self._baton_stream_reply = None
|
||||
if self.__base_url is not None:
|
||||
if self._base_url is not None:
|
||||
QTimer.singleShot(1000, self.start_baton_stream)
|
||||
|
||||
def _read_baton_stream(self, reply: QNetworkReply):
|
||||
@@ -2307,14 +2307,14 @@ class DAQWorker(QObject):
|
||||
"""
|
||||
Request the baton to gain write access to the beamline.
|
||||
"""
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info("POST /baton/request")
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/baton/request"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/baton/request"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
reply = self.__net_manager.post(request, QByteArray(b""))
|
||||
reply = self._net_manager.post(request, QByteArray(b""))
|
||||
reply.finished.connect(lambda: self._handle_baton_request_response(reply))
|
||||
|
||||
def _handle_baton_request_response(self, reply: QNetworkReply):
|
||||
@@ -2346,16 +2346,16 @@ class DAQWorker(QObject):
|
||||
Args:
|
||||
accept: True to grant the baton, False to refuse.
|
||||
"""
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info(f"POST /baton/respond?accept={accept}")
|
||||
return
|
||||
|
||||
request = QNetworkRequest(
|
||||
QUrl(f"{self.__base_url}/baton/respond?accept={str(accept).lower()}")
|
||||
QUrl(f"{self._base_url}/baton/respond?accept={str(accept).lower()}")
|
||||
)
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
reply = self.__net_manager.post(request, QByteArray(b""))
|
||||
reply = self._net_manager.post(request, QByteArray(b""))
|
||||
reply.finished.connect(lambda: self._handle_baton_response_result(reply))
|
||||
|
||||
def _handle_baton_response_result(self, reply: QNetworkReply):
|
||||
@@ -2386,12 +2386,12 @@ class DAQWorker(QObject):
|
||||
@Slot()
|
||||
def check_baton_timeout(self):
|
||||
"""Poll to check if timeout has been reached."""
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/baton/check_timeout"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/baton/check_timeout"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(request)
|
||||
reply.finished.connect(lambda: self._handle_baton_timeout_response(reply))
|
||||
|
||||
def _handle_baton_timeout_response(self, reply: QNetworkReply):
|
||||
@@ -2448,18 +2448,18 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot()
|
||||
def load_gui_sessions(self):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
self.gui_sessions_loaded.emit([])
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/admin/gui_sessions"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/admin/gui_sessions"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.get(request)
|
||||
reply.finished.connect(lambda: self._handle_gui_sessions_response(reply))
|
||||
|
||||
@Slot(int, int)
|
||||
def request_gui_close(self, session_id: int, grace_seconds: int = 60):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info(
|
||||
f"POST /admin/gui_sessions/{session_id}/request_close?grace_seconds={grace_seconds}"
|
||||
)
|
||||
@@ -2467,23 +2467,23 @@ class DAQWorker(QObject):
|
||||
|
||||
request = QNetworkRequest(
|
||||
QUrl(
|
||||
f"{self.__base_url}/admin/gui_sessions/{session_id}/request_close?grace_seconds={grace_seconds}"
|
||||
f"{self._base_url}/admin/gui_sessions/{session_id}/request_close?grace_seconds={grace_seconds}"
|
||||
)
|
||||
)
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
reply = self.__net_manager.post(request, QByteArray(b""))
|
||||
reply = self._net_manager.post(request, QByteArray(b""))
|
||||
reply.finished.connect(lambda: self._handle_gui_session_mutation_response(reply))
|
||||
|
||||
@Slot(int)
|
||||
def force_remove_gui_session(self, session_id: int):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
logger.info(f"DELETE /admin/gui_sessions/{session_id}")
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/admin/gui_sessions/{session_id}"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.deleteResource(request)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/admin/gui_sessions/{session_id}"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
reply = self._net_manager.deleteResource(request)
|
||||
reply.finished.connect(lambda: self._handle_gui_session_mutation_response(reply))
|
||||
|
||||
def _handle_gui_session_mutation_response(self, reply: QNetworkReply):
|
||||
@@ -2502,15 +2502,15 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot(int)
|
||||
def report_gui_interaction(self, session_id: int):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
return
|
||||
|
||||
request = QNetworkRequest(
|
||||
QUrl(f"{self.__base_url}/admin/gui_sessions/{session_id}/interaction")
|
||||
QUrl(f"{self._base_url}/admin/gui_sessions/{session_id}/interaction")
|
||||
)
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
reply = self.__net_manager.post(request, QByteArray(b""))
|
||||
reply = self._net_manager.post(request, QByteArray(b""))
|
||||
reply.finished.connect(lambda: reply.deleteLater())
|
||||
|
||||
def cleanup(self) -> None:
|
||||
@@ -2525,8 +2525,8 @@ class DAQWorker(QObject):
|
||||
logger.warning(f"Failed to stop _baton_timeout_timer: {e}")
|
||||
|
||||
try:
|
||||
if hasattr(self, "_DAQWorker__timer") and self.__timer is not None:
|
||||
self.__timer.stop()
|
||||
if hasattr(self, "_DAQWorker__timer") and self._timer is not None:
|
||||
self._timer.stop()
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to stop __timer: {e}")
|
||||
|
||||
|
||||
@@ -7,20 +7,20 @@ class JFJochDBusClient(QObject):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.__session_bus = QDBusConnection.sessionBus()
|
||||
self.__interface = None
|
||||
self.__dbus_available = False
|
||||
self._session_bus = QDBusConnection.sessionBus()
|
||||
self._interface = None
|
||||
self._dbus_available = False
|
||||
|
||||
self.__beamline = mx_beamline()
|
||||
self._beamline = mx_beamline()
|
||||
try:
|
||||
self.__detector_url = get_jfjoch_url(self.__beamline)
|
||||
self._detector_url = get_jfjoch_url(self._beamline)
|
||||
except ValueError:
|
||||
self.__detector_url = None
|
||||
self._detector_url = None
|
||||
|
||||
try:
|
||||
self.__session_bus = QDBusConnection.sessionBus()
|
||||
if self.__session_bus.isConnected():
|
||||
self.__dbus_available = True
|
||||
self._session_bus = QDBusConnection.sessionBus()
|
||||
if self._session_bus.isConnected():
|
||||
self._dbus_available = True
|
||||
print("D-Bus session bus connected")
|
||||
else:
|
||||
print("D-Bus not available")
|
||||
@@ -30,23 +30,23 @@ class JFJochDBusClient(QObject):
|
||||
|
||||
def _ensure_interface(self):
|
||||
"""Ensure we have a valid interface, creating one if needed."""
|
||||
if not self.__dbus_available:
|
||||
if not self._dbus_available:
|
||||
print("D-Bus not available, cannot connect to JFJoch viewer")
|
||||
return False
|
||||
|
||||
if self.__interface is None or not self.__interface.isValid():
|
||||
if not self.__session_bus.isConnected():
|
||||
if self._interface is None or not self._interface.isValid():
|
||||
if not self._session_bus.isConnected():
|
||||
print("Session bus not connected")
|
||||
return False
|
||||
|
||||
self.__interface = QDBusInterface(
|
||||
self._interface = QDBusInterface(
|
||||
"ch.psi.jfjoch_viewer", # Service name
|
||||
"/", # Object path
|
||||
"ch.psi.jfjoch_viewer", # Interface name
|
||||
self.__session_bus,
|
||||
self._session_bus,
|
||||
)
|
||||
|
||||
if not self.__interface.isValid():
|
||||
if not self._interface.isValid():
|
||||
print("Service 'ch.psi.jfjoch_viewer' not available")
|
||||
return False
|
||||
|
||||
@@ -54,7 +54,7 @@ class JFJochDBusClient(QObject):
|
||||
|
||||
@Slot(str, int)
|
||||
def load_image(self, name: str, number: int):
|
||||
if not self.__dbus_available:
|
||||
if not self._dbus_available:
|
||||
print("D-Bus not available, cannot load image in JFJoch viewer")
|
||||
return
|
||||
|
||||
@@ -64,13 +64,13 @@ class JFJochDBusClient(QObject):
|
||||
path = "/sls/mx/data/" + name + "_master.h5"
|
||||
print(f"Load dataset: {path} image: {number}")
|
||||
if self._ensure_interface():
|
||||
self.__interface.call("LoadFile", path, number, 1)
|
||||
self._interface.call("LoadFile", path, number, 1)
|
||||
|
||||
@Slot()
|
||||
def load_online(self):
|
||||
if not self.__dbus_available:
|
||||
if not self._dbus_available:
|
||||
print("D-Bus not available, cannot load online data in JFJoch viewer")
|
||||
return
|
||||
|
||||
if self._ensure_interface():
|
||||
self.__interface.call("LoadFile", self.__detector_url, -1, 1)
|
||||
self._interface.call("LoadFile", self._detector_url, -1, 1)
|
||||
|
||||
@@ -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,14 +155,14 @@ 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:
|
||||
if self._camera_available:
|
||||
self.viewport().setCursor(Qt.CursorShape.ArrowCursor)
|
||||
self.setToolTip("")
|
||||
else:
|
||||
@@ -179,58 +179,58 @@ class SampleCameraImageLabel(QGraphicsView):
|
||||
)
|
||||
|
||||
def __camera_interaction_enabled(self) -> bool:
|
||||
return self.__camera_available
|
||||
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
|
||||
tell_state = self._tell_state
|
||||
if tell_state is None:
|
||||
return "BEAMLINE BUSY"
|
||||
|
||||
@@ -243,10 +243,10 @@ class SampleCameraImageLabel(QGraphicsView):
|
||||
return f"TELL {activity_name}".upper()
|
||||
|
||||
def __draw_busy_overlay(self, painter: QPainter):
|
||||
if self.__busy_overlay_style is None:
|
||||
if self._busy_overlay_style is None:
|
||||
return
|
||||
|
||||
style = self.__busy_overlay_style
|
||||
style = self._busy_overlay_style
|
||||
|
||||
painter.save()
|
||||
painter.resetTransform()
|
||||
@@ -283,10 +283,10 @@ class SampleCameraImageLabel(QGraphicsView):
|
||||
painter.restore()
|
||||
|
||||
def __draw_session_overlay(self, painter: QPainter):
|
||||
if self.__busy_overlay_style is not None:
|
||||
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:
|
||||
@@ -334,7 +334,7 @@ class SampleCameraImageLabel(QGraphicsView):
|
||||
painter.restore()
|
||||
|
||||
def __draw_camera_unavailable_overlay(self, painter: QPainter):
|
||||
if self.__camera_available:
|
||||
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
|
||||
|
||||
@@ -462,59 +462,59 @@ class SampleCameraImageLabel(QGraphicsView):
|
||||
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
|
||||
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)
|
||||
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(
|
||||
@@ -578,7 +578,7 @@ class SampleCameraImageLabel(QGraphicsView):
|
||||
)
|
||||
|
||||
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):
|
||||
scene_rect = self.scene.sceneRect()
|
||||
@@ -591,7 +591,7 @@ class SampleCameraImageLabel(QGraphicsView):
|
||||
pixmap.save(file_path, "JPEG")
|
||||
|
||||
def __scaling(self):
|
||||
if not self.__autoscale:
|
||||
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():
|
||||
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,19 +686,19 @@ 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:
|
||||
if not self._show_detections:
|
||||
return
|
||||
if not getattr(self, "_SampleCameraImageLabel__detections", 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]
|
||||
)
|
||||
@@ -767,7 +767,7 @@ class SampleCameraImageLabel(QGraphicsView):
|
||||
"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:
|
||||
try:
|
||||
@@ -780,9 +780,9 @@ class SampleCameraImageLabel(QGraphicsView):
|
||||
return None
|
||||
|
||||
def __draw_target_point(self, painter: QPainter):
|
||||
if not self.__show_target_point:
|
||||
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()
|
||||
@@ -856,24 +856,24 @@ 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
|
||||
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]]:
|
||||
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)))
|
||||
@@ -920,7 +920,7 @@ class SampleCameraImageLabel(QGraphicsView):
|
||||
return lines
|
||||
|
||||
def __draw_overlay_legend(self, painter: QPainter):
|
||||
if not self.__legend_should_show():
|
||||
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:
|
||||
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
|
||||
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,8 +1056,8 @@ 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),
|
||||
)
|
||||
@@ -1087,35 +1087,35 @@ 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"))
|
||||
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)
|
||||
|
||||
@@ -15,9 +15,9 @@ class LoginDialog(QDialog):
|
||||
self.setWindowTitle("User Authentication")
|
||||
self.setMinimumWidth(400)
|
||||
self.setStyleSheet("background-color: rgb(216, 228, 253);")
|
||||
self.__base_url = base_url
|
||||
self.__reply = None
|
||||
self.__network_manager = None
|
||||
self._base_url = base_url
|
||||
self._reply = None
|
||||
self._network_manager = None
|
||||
|
||||
# Layout
|
||||
layout = QVBoxLayout(self)
|
||||
@@ -44,14 +44,14 @@ class LoginDialog(QDialog):
|
||||
|
||||
@Slot()
|
||||
def authenticate(self):
|
||||
if self.__base_url is None:
|
||||
if self._base_url is None:
|
||||
token_data = TokenData(
|
||||
sub=self.name_entry.text(), staff=True, session=15, pgroups=["p16371", "p22233"]
|
||||
)
|
||||
self.token = jwt.encode(token_data.model_dump(), "ABC123")
|
||||
self.accept()
|
||||
self.__network_manager = QNetworkAccessManager(self)
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/token"))
|
||||
self._network_manager = QNetworkAccessManager(self)
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/token"))
|
||||
request.setHeader(
|
||||
QNetworkRequest.KnownHeaders.ContentTypeHeader, "application/x-www-form-urlencoded"
|
||||
)
|
||||
@@ -60,17 +60,15 @@ class LoginDialog(QDialog):
|
||||
payload.addQueryItem("username", f"{self.name_entry.text()}")
|
||||
payload.addQueryItem("password", "")
|
||||
payload_string = payload.toString()
|
||||
self.__reply = self.__network_manager.post(
|
||||
request, QByteArray.fromStdString(payload_string)
|
||||
)
|
||||
self.__reply.finished.connect(self.handle_token_response)
|
||||
self._reply = self._network_manager.post(request, QByteArray.fromStdString(payload_string))
|
||||
self._reply.finished.connect(self.handle_token_response)
|
||||
|
||||
@Slot()
|
||||
def handle_token_response(self):
|
||||
if self.__reply is not None and self.__reply.error() == QNetworkReply.NetworkError.NoError:
|
||||
response_data = self.__reply.readAll().data()
|
||||
if self._reply is not None and self._reply.error() == QNetworkReply.NetworkError.NoError:
|
||||
response_data = self._reply.readAll().data()
|
||||
response_json = json.loads(response_data.decode("utf-8"))
|
||||
if "access_token" in response_json:
|
||||
self.token = response_json["access_token"]
|
||||
self.__reply.deleteLater()
|
||||
self._reply.deleteLater()
|
||||
self.accept()
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
from PySide6.QtCore import Signal, Slot
|
||||
from PySide6.QtWidgets import (
|
||||
QTableWidget,
|
||||
QHBoxLayout,
|
||||
QHeaderView,
|
||||
QPushButton,
|
||||
QTableWidget,
|
||||
QTableWidgetItem,
|
||||
QWidget,
|
||||
QHBoxLayout,
|
||||
QPushButton,
|
||||
)
|
||||
|
||||
from aare.gui.scan_logic.raster_grid_manager import RasterGridManager
|
||||
@@ -19,7 +19,7 @@ class RasterGridTable(QTableWidget):
|
||||
|
||||
def __init__(self, raster_mgr: RasterGridManager, parent=None):
|
||||
super().__init__(parent)
|
||||
self.__raster_mgr = raster_mgr
|
||||
self._raster_mgr = raster_mgr
|
||||
|
||||
self.setColumnCount(5)
|
||||
self.setHorizontalHeaderLabels(["Omega", "Chi", "Phi", "Size (WxH)", "Actions"])
|
||||
@@ -36,14 +36,14 @@ class RasterGridTable(QTableWidget):
|
||||
self.setMinimumHeight(100)
|
||||
|
||||
# Connect to raster manager signals
|
||||
self.__raster_mgr.completed_grid_updated.connect(self.refresh_table)
|
||||
self.copy_clicked.connect(self.__raster_mgr.completed_grid_redo)
|
||||
self.delete_clicked.connect(self.__raster_mgr.completed_grid_scan_delete)
|
||||
self.visible_clicked.connect(self.__raster_mgr.completed_grid_scan_toggle_visible)
|
||||
self.goto_clicked.connect(self.__raster_mgr.completed_grid_scan_goto)
|
||||
self._raster_mgr.completed_grid_updated.connect(self.refresh_table)
|
||||
self.copy_clicked.connect(self._raster_mgr.completed_grid_redo)
|
||||
self.delete_clicked.connect(self._raster_mgr.completed_grid_scan_delete)
|
||||
self.visible_clicked.connect(self._raster_mgr.completed_grid_scan_toggle_visible)
|
||||
self.goto_clicked.connect(self._raster_mgr.completed_grid_scan_goto)
|
||||
|
||||
def add_rows(self):
|
||||
for r in self.__raster_mgr.get_completed_grids():
|
||||
for r in self._raster_mgr.get_completed_grids():
|
||||
req = r.request
|
||||
|
||||
row_position = self.rowCount()
|
||||
|
||||
@@ -40,10 +40,10 @@ class StatusBar(QStatusBar):
|
||||
|
||||
def __init__(self, token: TokenData, parent=None):
|
||||
super().__init__(parent)
|
||||
self.__decoded_token = token
|
||||
self.__status = None
|
||||
self.__is_staff = self.__decoded_token.staff
|
||||
self.__allowed_pgroups = self.__decoded_token.pgroups
|
||||
self._decoded_token = token
|
||||
self._status = None
|
||||
self._is_staff = self._decoded_token.staff
|
||||
self._allowed_pgroups = self._decoded_token.pgroups
|
||||
|
||||
self._message_clear_timer = QTimer(self)
|
||||
self._message_clear_timer.setSingleShot(True)
|
||||
@@ -134,7 +134,7 @@ class StatusBar(QStatusBar):
|
||||
@Slot(DAQStatusModel)
|
||||
def update_daq_status(self, status: DAQStatusModel):
|
||||
try:
|
||||
self.__status = status
|
||||
self._status = status
|
||||
if status.bl.flux_ph_s is None:
|
||||
self.flux.set_value("0")
|
||||
else:
|
||||
@@ -300,10 +300,10 @@ class StatusBar(QStatusBar):
|
||||
|
||||
def _update_session_display(self):
|
||||
"""Update session label based on current status."""
|
||||
if self.__status is None:
|
||||
if self._status is None:
|
||||
return
|
||||
|
||||
session_state = self.__status.session.session
|
||||
session_state = self._status.session.session
|
||||
|
||||
# Base text
|
||||
if session_state == SessionsStateEnum.OwnedByYou:
|
||||
@@ -327,8 +327,8 @@ class StatusBar(QStatusBar):
|
||||
|
||||
def show_session_menu(self):
|
||||
menu = QMenu(self)
|
||||
is_busy = self.__status and self.__status.busy
|
||||
session_state = self.__status.session.session if self.__status else SessionsStateEnum.Vacant
|
||||
is_busy = self._status and self._status.busy
|
||||
session_state = self._status.session.session if self._status else SessionsStateEnum.Vacant
|
||||
# Determine if we are the holder or waiting for baton
|
||||
is_yours = session_state in (
|
||||
SessionsStateEnum.OwnedByYou,
|
||||
@@ -362,7 +362,7 @@ class StatusBar(QStatusBar):
|
||||
# Already have a pending request - show cancel option
|
||||
action_cancel = menu.addAction("Cancel Request")
|
||||
action_cancel.triggered.connect(self._on_cancel_request_clicked)
|
||||
elif self.__is_staff:
|
||||
elif self._is_staff:
|
||||
# Staff can always grab (override)
|
||||
action_grab = menu.addAction("Grab (Override)")
|
||||
action_grab.setEnabled(not is_busy) # Still respect busy for safety
|
||||
@@ -395,11 +395,11 @@ class StatusBar(QStatusBar):
|
||||
|
||||
# --- END SESSION (cleanup) ---
|
||||
action_end = menu.addAction("End Session")
|
||||
action_end.setEnabled(bool(is_yours or self.__is_staff))
|
||||
action_end.setEnabled(bool(is_yours or self._is_staff))
|
||||
action_end.triggered.connect(self.end_session_clicked)
|
||||
|
||||
# --- STAFF: FORCE GRAB (emergency) ---
|
||||
if self.__is_staff and is_other:
|
||||
if self._is_staff and is_other:
|
||||
menu.addSeparator()
|
||||
action_force = menu.addAction("⚠️ Force Take Over")
|
||||
action_force.triggered.connect(self._on_force_session_clicked)
|
||||
@@ -411,18 +411,16 @@ class StatusBar(QStatusBar):
|
||||
menu.exec()
|
||||
|
||||
def show_pgroup_menu(self):
|
||||
in_curr = self.__status
|
||||
in_curr = self._status
|
||||
|
||||
logger.info(f"in_curr is {in_curr}")
|
||||
|
||||
if not in_curr:
|
||||
print(f"is status None?: {self.__status}")
|
||||
print(f"is status None?: {self._status}")
|
||||
|
||||
allow_pgroup_menu = self.__is_staff or in_curr
|
||||
allow_pgroup_menu = self._is_staff or in_curr
|
||||
|
||||
owned_by_you = (
|
||||
self.__status and self.__status.session.session == SessionsStateEnum.OwnedByYou
|
||||
)
|
||||
owned_by_you = self._status and self._status.session.session == SessionsStateEnum.OwnedByYou
|
||||
|
||||
if allow_pgroup_menu:
|
||||
logger.info("showing pgroup menu")
|
||||
@@ -455,13 +453,13 @@ class StatusBar(QStatusBar):
|
||||
menu.exec()
|
||||
|
||||
def show_state_menu(self):
|
||||
if self.__status is None:
|
||||
if self._status is None:
|
||||
return
|
||||
|
||||
logger.info(self.__status.state)
|
||||
logger.info(self._status.state)
|
||||
menu = QMenu(self)
|
||||
|
||||
if self.__status.busy or self.__status.state in [BeamlineStateEnum.Moving]:
|
||||
if self._status.busy or self._status.state in [BeamlineStateEnum.Moving]:
|
||||
return
|
||||
|
||||
action_0 = menu.addAction("Available transitions")
|
||||
@@ -472,7 +470,7 @@ class StatusBar(QStatusBar):
|
||||
action_0.setEnabled(False)
|
||||
menu.addSeparator()
|
||||
|
||||
if self.__status.state in [
|
||||
if self._status.state in [
|
||||
BeamlineStateEnum.RobotSampleExchange,
|
||||
BeamlineStateEnum.SampleExchange,
|
||||
BeamlineStateEnum.DewarTransfer,
|
||||
@@ -483,14 +481,14 @@ class StatusBar(QStatusBar):
|
||||
]:
|
||||
action_1 = menu.addAction("Sample alignment")
|
||||
action_1.triggered.connect(self.sa)
|
||||
elif self.__status.state in [BeamlineStateEnum.SampleAlignment]:
|
||||
elif self._status.state in [BeamlineStateEnum.SampleAlignment]:
|
||||
action_2 = menu.addAction("Manual sample exchange")
|
||||
action_2.triggered.connect(self.se)
|
||||
action_3 = menu.addAction("Dewar transfer")
|
||||
action_3.triggered.connect(self.dl)
|
||||
action_4 = menu.addAction("Beam location")
|
||||
action_4.triggered.connect(self.beam_location)
|
||||
elif self.__status.state in [BeamlineStateEnum.Maintenance]:
|
||||
elif self._status.state in [BeamlineStateEnum.Maintenance]:
|
||||
action_2 = menu.addAction("Manual sample exchange")
|
||||
action_2.triggered.connect(self.se)
|
||||
|
||||
@@ -520,12 +518,12 @@ class StatusBar(QStatusBar):
|
||||
self._show_pgroup_after_baton_grant()
|
||||
|
||||
def show_change_dialog(self):
|
||||
logger.debug(self.__decoded_token.pgroups)
|
||||
curr = self.__status.session.current_pgroup
|
||||
logger.debug(self._decoded_token.pgroups)
|
||||
curr = self._status.session.current_pgroup
|
||||
pgroups = [
|
||||
str(p) for p in (self.__allowed_pgroups or []) if p is not None and str(p).strip()
|
||||
str(p) for p in (self._allowed_pgroups or []) if p is not None and str(p).strip()
|
||||
]
|
||||
if self.__is_staff:
|
||||
if self._is_staff:
|
||||
|
||||
def _on_loaded(lst: list):
|
||||
try:
|
||||
@@ -543,7 +541,7 @@ class StatusBar(QStatusBar):
|
||||
pass
|
||||
|
||||
self.staff_pgroups_loaded.connect(_on_loaded)
|
||||
self.__list_staff_pgroups()
|
||||
self._list_staff_pgroups()
|
||||
return
|
||||
|
||||
self._generate_pgroup_dialogue(curr=curr, pgroups=pgroups)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from PySide6.QtCore import Signal, Qt
|
||||
from PySide6.QtCore import Qt, Signal
|
||||
from PySide6.QtWidgets import QLabel
|
||||
|
||||
|
||||
@@ -7,16 +7,16 @@ class ValueLabel(QLabel):
|
||||
|
||||
def __init__(self, text: str, unit: str = "", parent=None):
|
||||
super().__init__(parent)
|
||||
self.__descr = text
|
||||
self.__unit = unit
|
||||
self._descr = text
|
||||
self._unit = unit
|
||||
|
||||
def set_value(self, s: str, color: str | None = None):
|
||||
if color is not None:
|
||||
self.setText(
|
||||
f"""{self.__descr}: <span style="color: {color};"><b>{s}</b> {self.__unit} </span>"""
|
||||
f"""{self._descr}: <span style="color: {color};"><b>{s}</b> {self._unit} </span>"""
|
||||
)
|
||||
else:
|
||||
self.setText(f"{self.__descr}: <b>{s}</b> {self.__unit}")
|
||||
self.setText(f"{self._descr}: <b>{s}</b> {self._unit}")
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
if event.button() == Qt.MouseButton.LeftButton:
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user