DAQ: changs to face detection, mlbox, measure for automation tests

This commit is contained in:
2026-04-01 17:03:13 +02:00
parent 47324a79ae
commit edee7082fd
+92 -36
View File
@@ -443,8 +443,12 @@ class AareDAQ:
try:
value = self.__devs.tell.mount(address=target.tell_address(), force=True, auto_unmount=True, read_dm=False,
wait=True, timeout=360.0)
if self.__cfg.current_sample is not None and self.__cfg.current_sample.db_id is not None:
self.__aare.sample_unmounted(self.__cfg.current_sample)
logger.info(f"Mount result: {value}")
self.__cfg.current_sample = target
if self.__cfg.current_sample is not None and self.__cfg.current_sample.db_id is not None:
self.__aare.sample_mounted(self.__cfg.current_sample)
except Exception as e:
logger.error(f"Mount failed: {e}")
if self.__cfg.current_sample is not None and self.__cfg.current_sample.db_id is not None:
@@ -702,14 +706,21 @@ class AareDAQ:
else:
return None
def __setup_datacollection(self, request: RasterGridRequest | RasterGridRequest):
def __setup_datacollection(self, request: RasterGridRequest | RasterGridRequest, screening: bool = False):
if request.dtz is not None:
logger.info(f'requesting dtz to move to {request.dtz}')
self.__cfg.dtz = request.dtz
if self.sample is not None and self.sample.db_id is not None:
self.save_screenshot_db(self.sample.db_id, f"{self.sample.db_id}_before_data_collection")
sample_id = self.sample.db_id
if screening:
screenshot_name = f"{sample_id}_before_screening"
elif type(request) is RasterGridRequest:
screenshot_name = f"{sample_id}_before_raster"
else:
screenshot_name = f"{sample_id}_before_data_collection"
self.save_screenshot_db(sample_id, screenshot_name)
self.__set_state(BeamlineStateEnum.DataCollection)
@@ -769,12 +780,13 @@ class AareDAQ:
def __raster(self, request: RasterGridRequest) -> CompletedRasterGridElem:
self.__devs.aerotech_omega = request.omega_deg
previous_smargon_pos = self.__devs.smargon_pos
self.__setup_datacollection(request=request)
status = self.status
logger.info(f"raster status {status}")
logger.info(f'raster grid request: {request}')
total_time = request.exp_time_s*request.n_x*request.n_y
total_time = request.exp_time_s*request.n_x*request.n_y+request.n_y*0.3
result = None
if not self.__cfg.simulated_detector:
pass
@@ -789,6 +801,8 @@ class AareDAQ:
logger.info("Simulated detector mode enabled; returning fake zero raster result.")
try:
if self.sample is not None and self.sample.db_id is not None:
self.__aare.create_gridscan_run(self.sample, request, status)
self.__devs.aerotech.grid_scan(grid_elem_size_y_um=request.grid_size_mm.y*1000,
grid_elem_size_x_um=request.grid_size_mm.x*1000,
grid_elem_count_x=request.n_x,
@@ -798,7 +812,27 @@ class AareDAQ:
self.__devs.aerotech.wait_till_done(timeout=int(round(total_time*2,0)))
self.__devs.aerotech_pos = self.__cfg.abr_meas_pos
#go back to aerotech x,y,z home not U home (0 degrees).
if type(self.__cfg.abr_meas_pos) is Coordinate:
coord = self.__cfg.abr_meas_pos
else:
coord = self.__cfg.abr_meas_pos.at_mm
self.__devs.aerotech_pos = AerotechCoordinate(at_mm=coord, omega_deg=self.__devs.aerotech_omega)
self.__devs.smargon_pos = previous_smargon_pos
self.__devs.smargon_wait(timeout=180)
result = self._build_fake_raster_result(request)
try:
sample_id = self.sample.db_id if self.sample is not None and self.sample.db_id is not None else None
if sample_id:
self.save_screenshot_db(sample_id, f"{sample_id}_post_raster_{request.omega_deg}deg")
self.__aare.ingest_gridscan(sample = self.sample, raster_result = result.result,
raster_request = request, geom = self.sample_geometry,
com = None, beam_mark_pxl=self.__cfg.get_beam_mark(self.zoom))
except Exception as e:
logger.error(f"Exception ingesting grid scan: {e}")
# try:
# result = self.__jfjoch.wait_till_done(60)
@@ -808,7 +842,7 @@ class AareDAQ:
if result is None:
logger.warning("JFJoch returned no ScanResult; using fake result for raster scan.")
return self._build_fake_raster_result(request)
return result
#return CompletedRasterGridElem(request=copy.deepcopy(request), result=result, centre_of_mass=None)
@@ -858,7 +892,8 @@ class AareDAQ:
#self.__aare.create_rotation_run(self.sample, request, status)
total_time = request.exp_time_s * request.steps
if self.sample is not None and self.sample.db_id is not None:
self.__aare.create_rotation_run(self.sample, request, status)
try:
if self.__cfg.simulated_detector:
@@ -905,22 +940,23 @@ class AareDAQ:
# else:
# # Let JFJochCommunicationError propagate
# result = self.__jfjoch.wait_till_done(60)
self.__set_state(BeamlineStateEnum.SampleAlignment)
if self.sample is not None and self.sample.db_id is not None:
self.save_screenshot_db(self.sample.db_id, "scan_preview")
try:
self.__aare.sample_collected(self.sample)
self.__aare.ingest_scan(sample=self.sample, result=result.result,
geom=self.sample_geometry, beam_mark_pxl=self.__cfg.get_beam_mark(self.zoom))
# if self.sample is not None and self.sample.db_id is not None:
# self.save_screenshot_db(self.sample.db_id, "after_dc")
# try:
# self.__aare.ingest_scan(sample=self.sample, result=result,
# geom=self.sample_geometry, beam_mark_pxl=self.__cfg.get_beam_mark(self.zoom))
#
# except Exception as e:
# logger.error(f"Exception ingesting scan: {e}")
except Exception as e:
logger.error(f"Exception ingesting scan: {e}")
except JFJochCommunicationError:
raise
except Exception as e:
logger.error(f"Exception during rotation scan: {e}")
raise
return CompletedRotationScan(request=copy.deepcopy(request), result=result)
return result
def measure_rotation(self, request: RotationScanRequest) -> CompletedRotationScan:
"""
@@ -1162,13 +1198,16 @@ class AareDAQ:
self.__cfg.state_busy = False
raise
def face_detection(self, steps: int = 14, step_size: int = 15) -> dict:
def face_detection(self, steps: int = 14, step_size: int = 15, face_min_ratio: float =0.3) -> 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.
steps: Number of rotation steps. Default is 14.
step_size: Size of each rotation step in degrees. Default is 15.
face_min_ratio: Minimum ratio of loopface count to loop_all count to use loop_face over loop_all.
i.e. if 10 loop_face vs 4 loop_all pick loop_face. if 2 loop_face and 12 loop_all use loop_all.
Default is 0.3.
Returns:
Dictionary containing face detection results, including found samples and fits.
@@ -1176,7 +1215,7 @@ class AareDAQ:
self.__cfg.try_set_busy(timeout=360)
try:
logger.info("running face detection sequence")
result = self.__face_detection_sequence(steps=steps, step_size=step_size)
result = self.__face_detection_sequence(steps=steps, step_size=step_size,face_min_ratio=face_min_ratio)
except Exception as e:
logger.error(f"error in face detection sequence {e}")
result = {
@@ -1208,7 +1247,7 @@ class AareDAQ:
return
def __face_detection_sequence(self, steps: int = 14, step_size: int = 15) -> dict:
def __face_detection_sequence(self, steps: int = 14, step_size: int = 15, face_min_ratio: float = 0.3) -> dict:
self.__set_state(BeamlineStateEnum.SampleAlignment)
self.__devs.lamp_light = 2.5
self.__cfg.zoom_mode = ZoomModeEnum.LoopCenter
@@ -1219,7 +1258,8 @@ class AareDAQ:
self.__devs.set_zoom(zoom_value, wait=True)
boxes: dict[int, tuple[float, float, float, float]] = {}
boxes_face: dict[int, tuple[float, float, float, float]] = {}
boxes_loop: dict[int, tuple[float, float, float, float]] = {}
curr_angle = int(self.__devs.aerotech_omega)
total_range = steps * step_size + 1
start_angle = curr_angle if curr_angle + total_range < 720 else 0
@@ -1232,6 +1272,8 @@ class AareDAQ:
logger.info(f"time to rotate 15 degrees: {time.perf_counter() - rotate_time}")
curr_image = cv2.cvtColor(self.camera_image, cv2.COLOR_RGB2BGR)
if self.sample is not None and self.sample.db_id is not None:
self.save_screenshot_db(self.sample.db_id, f"fd_{self.sample.db_id}_{angle}deg")
box_time = time.perf_counter()
m = self.__mlbox.predict(curr_image, filename=None, preferred_class=(3, 0))
logger.info(f"time to predict: {time.perf_counter() - box_time}")
@@ -1241,7 +1283,7 @@ class AareDAQ:
self._emit_face_detection_progress({
"running": True,
"current_angle_deg": angle,
"samples": fd.get_samples_out(boxes),
"samples": fd.get_samples_out(boxes_face),
"height_fit": {},
"area_fit": {},
})
@@ -1253,25 +1295,42 @@ class AareDAQ:
self.face_detection_centre_correction(m, tolerance=0.2)
if cls_id == 3:
boxes[angle] = (x1, y1, x2, y2)
boxes_face[angle] = (x1, y1, x2, y2)
logger.info(f"accepted box at angle {angle}, cls={cls_id}, box={(x1, y1, x2, y2)}")
elif cls_id == 0:
boxes_loop[angle] = (x1, y1, x2, y2)
logger.info(f"accepted box at angle {angle}, cls={cls_id}, box={(x1, y1, x2, y2)}")
else:
logger.debug(f"ignoring class {cls_id} (pin/crystal) at angle {angle}")
logger.debug(f"ignoring class {cls_id} at angle {angle}")
self._emit_face_detection_progress({
"running": True,
"current_angle_deg": angle,
"samples": fd.get_samples_out(boxes),
"samples": fd.get_samples_out(boxes_face),
"height_fit": {},
"area_fit": {},
})
if not boxes:
if not boxes_face and not boxes_loop:
logger.info("no boxes found")
result = {"running": False, "samples": [], "height_fit": {}, "area_fit": {}}
self._emit_face_detection_progress(result)
return result
total_detections = len(boxes_face) + len(boxes_loop)
face_ratio = len(boxes_face) / total_detections if total_detections > 0 else 0.0
if boxes_face and face_ratio >= face_min_ratio:
boxes = boxes_face
logger.info(f"using loop_face boxes ({len(boxes_face)}/{total_detections}, ratio={face_ratio:.2f})")
elif boxes_loop:
boxes = boxes_loop
logger.info(
f"falling back to loop_all boxes ({len(boxes_loop)}/{total_detections}, ratio={1 - face_ratio:.2f})")
else:
boxes = boxes_face
logger.info(f"using loop_face boxes (only source, {len(boxes_face)} entries)")
best_fit_angle_area, area_params = fd.get_flat_face(boxes, start_angle, end_angle, True)
best_fit_angle_height, height_params = fd.get_flat_face(boxes, start_angle, end_angle, False)
fit_results = {
@@ -1351,6 +1410,7 @@ class AareDAQ:
filename = f"{sample_id}_{angle}_{zoom_value:.0f}" if sample_id is not None else None
try:
self.save_screenshot(filename=f'{sample_id}_{angle}')
target, cls, classes = self.__ml_loop_centre_box(sample_id, filename)
except Exception as e:
logger.error(f"Error getting ML box for angle {angle}")
@@ -1403,6 +1463,7 @@ class AareDAQ:
#i += 1
logger.debug("alc success")
logger.debug(f"current sample: {self.__cfg.current_sample}, sample_id of scan: {sample_id}")
self.__aare.sample_centered(self.__cfg.current_sample)
if sample_id is not None:
logger.info(f"sample {sample_id} centered")
@@ -1486,7 +1547,8 @@ class AareDAQ:
def save_screenshot(self, filename: str):
#time.sleep(0.2) # Wait 200 ms to ensure camera image is stable
bgr_image = cv2.cvtColor(self.camera_image, cv2.COLOR_RGB2BGR)
#cv2.imwrite(f"{filename}.jpg", bgr_image)
logger.debug(f"saving screenshot {filename}")
cv2.imwrite(f"/sls/mx/applications/logs/{filename}.jpg", bgr_image)
def save_screenshot_db(self, sample_id: int, filename: str):
"""
@@ -1642,9 +1704,6 @@ class AareDAQ:
start_mount=time.perf_counter()
logger.info(f"starting mount {sample.db_id} at {time.ctime()}")
self.__mount(sample)
if sample.db_id is not None:
self.__aare.sample_mounted(sample)
self.save_screenshot_db(sample.db_id, f"{sample.db_id}_mounted")
logger.info(f"mounting done at {time.perf_counter() - start_mount}, total time: {time.perf_counter() - start}")
#self.__devs.smargon_pos
#self.__devs.aerotech_pos =
@@ -1695,12 +1754,9 @@ class AareDAQ:
transmission=params.transmission,
))
logger.info(f"rotation done at {time.perf_counter() - start}")
if sample.db_id is not None:
self.__aare.sample_collected(sample)
self.save_screenshot_db(sample.db_id, f"{sample.db_id}_collected")
self.__cfg.state_busy = False
end = time.perf_counter()
return end - start
self.__cfg.state_busy = False
end = time.perf_counter()
return end - start
else:
logger.error("auto center failed")
self.__aare.axc_failed(sample)