CHANGELOG.md updated and added autodocstrings to daq daq_worker and server.py
This commit is contained in:
+21
-5
@@ -2,34 +2,50 @@
|
||||
## 0.3.0
|
||||
- Merge three repos into one, remove Ultralytics as dependency
|
||||
- **DAQ/Server**:
|
||||
- Added lifespan function for one-time initialization of core objects (devices, daq, config) on startup, reducing conflicting Redis and device connections.
|
||||
- Big Aerotech upgrade: transitioned to Aerotech API with ported coordinate functions.
|
||||
- Re-enabled and updated automation workflow 2.0 with new backend endpoints.
|
||||
- Added `park_and_dry` functionality for repeated mount failures.
|
||||
- Workflow assertions to ensure scintillator is down when possible.
|
||||
- Improved aerotech movement and position calculations.
|
||||
- Implementation of camera auto-exposure and zoom-dependent settings.
|
||||
- Added SAS-TT school demo temporary raster function.
|
||||
- Enhanced status polling with robust error handling for `Tell` and `Smargon` communication.
|
||||
- `ml_box` updated with beamline-specific URLs and model class types.
|
||||
- Enhanced status polling with robust error handling for `Tell`, `Smargon`, and `Aerotech` communication.
|
||||
- `ml_box` updated with beamline-specific URLs, model class types, and new YOLO26 engine.
|
||||
- Added separate authentication pathway for staff-only beamline recovery operations.
|
||||
- Improved database message limits for omega (to 2 decimal places).
|
||||
- Added screenshot to database from GUI; face detection sequence and beamline recovery updates.
|
||||
- Throttled `UserRightsException` logging to reduce log noise.
|
||||
- **Baton & Sessions**:
|
||||
- Major rework of the baton exchange procedure and session management.
|
||||
- Added `NOT_BATON_HOLDER` error code and specialized handling for baton requests.
|
||||
- Integrated baton status messages into the camera image display and status bar.
|
||||
- Improved session state transitions with new `Pending` states and P-group selection logic.
|
||||
- **GUI**:
|
||||
- Added `ui_state_manager` to handle `QSettings` for persistent UI state.
|
||||
- Major rework of `predict_subscriber` and prediction stream updates; corrected default ZMQ stream for ML box.
|
||||
- Added FPS counter and sharpness monitoring for sample camera.
|
||||
- Improved camera thread performance and authentication failure handling.
|
||||
- Added X10SA specific camera and prediction configurations.
|
||||
- New `display_status` for user-facing connection/error messages.
|
||||
- Integrated `dev_help` panel with logs and error code search.
|
||||
- Added framework for building GUI tutorials.
|
||||
- Added alert banner for critical disconnection issues (server/Tell).
|
||||
- Added framework for building GUI tutorials with first draft of X10SA tutorial.
|
||||
- Added alert banner for critical disconnection issues (server/Tell/Aerotech).
|
||||
- New screenshot button and controls in sample camera GUI.
|
||||
- Added SSE support for face detection panel updates during automation.
|
||||
- Integrated sample position resync in recovery panel to reduce Tell polling.
|
||||
- **TELL**:
|
||||
- Fixed matplotlib warnings in face detection panel and improved layout persistence.
|
||||
- **TELL & Infrastructure**:
|
||||
- Added mTLS certificate authentication for `aaredb`, `tellupdater`, and `spreadsheetupdater`.
|
||||
- Improved SSE client resilience in `tellupdater` with retry mechanisms and enhanced error logging.
|
||||
- Major refactor: removed redundant code and added simulated client.
|
||||
- Converted wait functions to use inbuilt pshell functionality.
|
||||
- Added error handling and proxy support for connection failures.
|
||||
- Fixed event names for mounting, and added missing events for gripper detection and motion sync.
|
||||
- Refactored SSE connection handling for simplicity and robustness.
|
||||
- **Common & Devices**:
|
||||
- Added BEC worker and Aerotech controller initialization in `devices.py`.
|
||||
- Added magnet position sensor functions and centralized transmission settings.
|
||||
- Added centralized `error_codes` and refactored exception handling to use FastAPI handlers.
|
||||
- Expanded device support with new PVs (beamstop, scintillator, collimator, cryojet, etc.).
|
||||
- Cleaned up `mx_lib` wait functions and `my_motor` class; added `clean_filename` helper.
|
||||
|
||||
@@ -812,6 +812,16 @@ class AareDAQ:
|
||||
raise Exception(f"Failed during raster: {e}") from e
|
||||
|
||||
def measure_raster(self, r: RasterGridRequest, auto: bool) -> CompletedRasterGrid:
|
||||
"""
|
||||
Execute a raster scan.
|
||||
|
||||
Args:
|
||||
r: RasterGridRequest parameters for the scan.
|
||||
auto: Boolean flag indicating if this is part of an automated sequence.
|
||||
|
||||
Returns:
|
||||
CompletedRasterGrid result.
|
||||
"""
|
||||
self.__cfg.try_set_busy(timeout=ceil(360))
|
||||
try:
|
||||
if auto:
|
||||
@@ -906,6 +916,15 @@ class AareDAQ:
|
||||
return CompletedRotationScan(request=copy.deepcopy(request), result=result)
|
||||
|
||||
def measure_rotation(self, request: RotationScanRequest) -> CompletedRotationScan:
|
||||
"""
|
||||
Execute a rotation scan.
|
||||
|
||||
Args:
|
||||
request: RotationScanRequest parameters.
|
||||
|
||||
Returns:
|
||||
CompletedRotationScan result.
|
||||
"""
|
||||
total_time = request.exp_time_s * request.steps
|
||||
logger.info(f"received rotation scan request: {request}, total time: {total_time}s, steps: {request.steps}")
|
||||
self.__cfg.try_set_busy(timeout=ceil(total_time + 360))
|
||||
@@ -1117,6 +1136,16 @@ class AareDAQ:
|
||||
return SmargonCoordinate(sh_mm=coord), cls, classes
|
||||
|
||||
def ml_bounding_box(self, sample_id: int | None = None, filename: str | None = None) -> RasterGridRequest | None:
|
||||
"""
|
||||
Request an ML-based bounding box for the sample.
|
||||
|
||||
Args:
|
||||
sample_id: Optional sample ID.
|
||||
filename: Optional filename for saving/logging.
|
||||
|
||||
Returns:
|
||||
RasterGridRequest object representing the found bounding box, or None if failed.
|
||||
"""
|
||||
try:
|
||||
self.__cfg.try_set_busy(timeout=360)
|
||||
r = self.__ml_bounding_box(sample_id, filename)
|
||||
@@ -1127,6 +1156,16 @@ class AareDAQ:
|
||||
raise
|
||||
|
||||
def face_detection(self, steps: int = 14, step_size: int = 15) -> dict:
|
||||
"""
|
||||
Perform a face detection sequence by rotating the sample and using ML to find the flat face.
|
||||
|
||||
Args:
|
||||
steps: Number of rotation steps.
|
||||
step_size: Size of each rotation step in degrees.
|
||||
|
||||
Returns:
|
||||
Dictionary containing face detection results, including found samples and fits.
|
||||
"""
|
||||
self.__cfg.try_set_busy(timeout=360)
|
||||
try:
|
||||
logger.info("running face detection sequence")
|
||||
@@ -1391,6 +1430,19 @@ class AareDAQ:
|
||||
return False
|
||||
|
||||
def auto_loop_center(self, sample_id: int | None = None) -> float:
|
||||
"""
|
||||
Automatically center the loop using ML-based detection.
|
||||
This performs a multi-step sequence including rotation and centering.
|
||||
|
||||
Args:
|
||||
sample_id: Optional database ID of the sample. If None, uses current sample.
|
||||
|
||||
Returns:
|
||||
Time taken for the centering process (seconds)
|
||||
|
||||
Raises:
|
||||
LoopCenteringFailed: If the centering sequence fails
|
||||
"""
|
||||
start = time.perf_counter()
|
||||
try:
|
||||
self.__cfg.try_set_busy(timeout=360)
|
||||
@@ -1430,6 +1482,13 @@ class AareDAQ:
|
||||
cv2.imwrite(f"{filename}.jpg", bgr_image)
|
||||
|
||||
def save_screenshot_db(self, sample_id: int, filename: str):
|
||||
"""
|
||||
Capture a screenshot and upload it to the database for a specific sample.
|
||||
|
||||
Args:
|
||||
sample_id: Database ID of the sample.
|
||||
filename: Name to give to the uploaded image.
|
||||
"""
|
||||
#time.sleep(0.2) # Wait 200 ms to ensure camera image is stable
|
||||
bgr_image = cv2.cvtColor(self.camera_image, cv2.COLOR_RGB2BGR)
|
||||
self.__aare.upload_image(sample_id, filename, bgr_image)
|
||||
@@ -1549,6 +1608,16 @@ class AareDAQ:
|
||||
return default_params, "defaults"
|
||||
|
||||
def measure(self, sample: SampleShortInfo) -> float:
|
||||
"""
|
||||
Main measurement sequence for a given sample.
|
||||
Includes mounting, loop centering, face detection, auto-raster, and data collection.
|
||||
|
||||
Args:
|
||||
sample: SampleShortInfo object containing sample details
|
||||
|
||||
Returns:
|
||||
Total time taken for the measurement (seconds)
|
||||
"""
|
||||
start = time.perf_counter()
|
||||
formatted_date = datetime.now().strftime('%Y%m%d')
|
||||
sample_prefix = "{}/{}/{:02d}/{}".format(
|
||||
|
||||
+779
-5
File diff suppressed because it is too large
Load Diff
@@ -68,6 +68,14 @@ class DAQWorker(QObject):
|
||||
baton_timeout_checked = Signal(dict)
|
||||
|
||||
def __init__(self, base_url: str | None, token: str, parent=None):
|
||||
"""
|
||||
Initialize the DAQWorker.
|
||||
|
||||
Args:
|
||||
base_url: The base URL of the DAQ server.
|
||||
token: The authentication token.
|
||||
parent: The parent QObject.
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self._active_status_error_key = None
|
||||
self.__token = token
|
||||
@@ -230,6 +238,9 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot()
|
||||
def regular_update(self):
|
||||
"""
|
||||
Periodically triggered slot to update spreadsheet and request status from server.
|
||||
"""
|
||||
if self.__counter % SPREADHSEET_FREQUENCY == 0:
|
||||
self.load_spreadsheet()
|
||||
self.load_reference_tools()
|
||||
@@ -237,6 +248,9 @@ class DAQWorker(QObject):
|
||||
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:
|
||||
return
|
||||
|
||||
@@ -252,6 +266,18 @@ class DAQWorker(QObject):
|
||||
|
||||
@staticmethod
|
||||
def handle_response(reply: QNetworkReply):
|
||||
"""
|
||||
Common handler for QNetworkReply to read data and handle errors.
|
||||
|
||||
Args:
|
||||
reply: The QNetworkReply object.
|
||||
|
||||
Returns:
|
||||
The response data as a UTF-8 string.
|
||||
|
||||
Raises:
|
||||
RuntimeError: If the reply contains a network error.
|
||||
"""
|
||||
if reply.error() == QNetworkReply.NetworkError.NoError:
|
||||
response_data = reply.readAll().data().decode("utf-8")
|
||||
reply.deleteLater()
|
||||
@@ -320,6 +346,12 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot(QNetworkReply)
|
||||
def handle_status_response(self, reply: QNetworkReply):
|
||||
"""
|
||||
Handle the server's response to a status request.
|
||||
|
||||
Args:
|
||||
reply: The QNetworkReply object.
|
||||
"""
|
||||
try:
|
||||
response_data = self.handle_response(reply)
|
||||
parsed_response = DAQStatusModel.model_validate_json(response_data)
|
||||
@@ -546,6 +578,13 @@ class DAQWorker(QObject):
|
||||
self.http_error.emit(str(e))
|
||||
|
||||
def generic_post(self, url: str, body: str = ""):
|
||||
"""
|
||||
Send a generic HTTP POST request to the server.
|
||||
|
||||
Args:
|
||||
url: The relative URL (endpoint).
|
||||
body: The request body string.
|
||||
"""
|
||||
if self.__base_url is None:
|
||||
logger.info(f"POST /{url}: {body}")
|
||||
return
|
||||
@@ -558,6 +597,13 @@ class DAQWorker(QObject):
|
||||
reply.finished.connect(lambda: self.handle_req_response(reply))
|
||||
|
||||
def generic_put(self, url: str, body: str = ""):
|
||||
"""
|
||||
Send a generic HTTP PUT request to the server.
|
||||
|
||||
Args:
|
||||
url: The relative URL (endpoint).
|
||||
body: The request body string.
|
||||
"""
|
||||
if self.__base_url is None:
|
||||
logger.info(f"PUT /{url}: {body}")
|
||||
return
|
||||
@@ -569,6 +615,12 @@ class DAQWorker(QObject):
|
||||
reply.finished.connect(lambda: self.handle_req_response(reply))
|
||||
|
||||
def generic_delete(self, url: str):
|
||||
"""
|
||||
Send a generic HTTP DELETE request to the server.
|
||||
|
||||
Args:
|
||||
url: The relative URL (endpoint).
|
||||
"""
|
||||
if self.__base_url is None:
|
||||
logger.info(f"DELETE /{url}")
|
||||
return
|
||||
@@ -778,6 +830,12 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot(RotationScanRequest)
|
||||
def standard_scan(self, r: RotationScanRequest):
|
||||
"""
|
||||
Trigger a standard rotation scan.
|
||||
|
||||
Args:
|
||||
r: The RotationScanRequest object.
|
||||
"""
|
||||
self.run_number_incremented.emit()
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/scan/rotation"))
|
||||
@@ -826,6 +884,12 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot(RasterGridRequest)
|
||||
def raster_scan(self, r: RasterGridRequest):
|
||||
"""
|
||||
Trigger a raster scan.
|
||||
|
||||
Args:
|
||||
r: The RasterGridRequest object.
|
||||
"""
|
||||
self.run_number_incremented.emit()
|
||||
if self.__base_url is None:
|
||||
logger.info(f"POST /scan/raster: {r.model_dump_json()}")
|
||||
@@ -861,6 +925,12 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot(RasterGridRequest)
|
||||
def raster_scan_auto(self, r: RasterGridRequest):
|
||||
"""
|
||||
Trigger an automated raster scan.
|
||||
|
||||
Args:
|
||||
r: The RasterGridRequest object.
|
||||
"""
|
||||
self.run_number_incremented.emit()
|
||||
if self.__base_url is None:
|
||||
logger.info(f"POST /scan/raster: {r.model_dump_json()}")
|
||||
@@ -955,6 +1025,12 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot(SampleShortInfo)
|
||||
def automated_scan(self, s: SampleShortInfo):
|
||||
"""
|
||||
Trigger a fully automated scan sequence for a sample.
|
||||
|
||||
Args:
|
||||
s: The SampleShortInfo object.
|
||||
"""
|
||||
if self.__base_url is None:
|
||||
logger.info(f"POST /scan/auto: {s.model_dump_json()}")
|
||||
return
|
||||
@@ -1045,6 +1121,9 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot()
|
||||
def ml_bounding_box(self):
|
||||
"""
|
||||
Request an ML-based bounding box for the current sample.
|
||||
"""
|
||||
if self.__base_url is None:
|
||||
logger.info(f"POST /alc/ml_bounding_box")
|
||||
return
|
||||
@@ -1102,6 +1181,13 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot()
|
||||
def face_detection(self, steps: int, step_size: int):
|
||||
"""
|
||||
Trigger the face detection procedure.
|
||||
|
||||
Args:
|
||||
steps: Number of rotation steps.
|
||||
step_size: Degrees per step.
|
||||
"""
|
||||
if self.__base_url is None:
|
||||
logger.info(f"POST /face_detection/run?steps={steps}&step_size={step_size}")
|
||||
return
|
||||
@@ -1355,7 +1441,9 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot()
|
||||
def request_baton(self):
|
||||
"""Request the baton."""
|
||||
"""
|
||||
Request the baton to gain write access to the beamline.
|
||||
"""
|
||||
if self.__base_url is None:
|
||||
logger.info("POST /baton/request")
|
||||
return
|
||||
@@ -1388,7 +1476,12 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot(bool)
|
||||
def respond_to_baton_request(self, accept: bool):
|
||||
"""Respond to an incoming baton request."""
|
||||
"""
|
||||
Respond to an incoming baton request from another user.
|
||||
|
||||
Args:
|
||||
accept: True to grant the baton, False to refuse.
|
||||
"""
|
||||
if self.__base_url is None:
|
||||
logger.info(f"POST /baton/respond?accept={accept}")
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user