diff --git a/src/aare/daq/daq.py b/src/aare/daq/daq.py index 1fdd97ff..355a2e37 100644 --- a/src/aare/daq/daq.py +++ b/src/aare/daq/daq.py @@ -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)