def CropFoV(ccd, dfbkg=None): """ Limit the ccd FoV to just contain the spot, in order to save some time on AutoFocus process. ccd (model.DigitalCamera): The CCD """ image = AcquireNoBackground(ccd, dfbkg) center_pxs = ((image.shape[1] / 2), (image.shape[0] / 2)) try: spot_pxs = FindSpot(image) except LookupError: logging.warning("Couldn't locate spot when cropping CCD image, will use whole FoV") ccd.binning.value = (1, 1) ccd.resolution.value = ccd.resolution.range[1] return tab_pxs = [a - b for a, b in zip(spot_pxs, center_pxs)] max_dim = int(max(abs(tab_pxs[0]), abs(tab_pxs[1]))) range_x = (ccd.resolution.range[0][0], ccd.resolution.range[1][0]) range_y = (ccd.resolution.range[0][1], ccd.resolution.range[1][1]) ccd.resolution.value = (sorted((range_x[0], 2 * max_dim + FOV_MARGIN, range_x[1]))[1], sorted((range_y[0], 2 * max_dim + FOV_MARGIN, range_y[1]))[1]) ccd.binning.value = (1, 1)
def CropFoV(ccd, dfbkg=None): """ Limit the ccd FoV to just contain the spot, in order to save some time on AutoFocus process. ccd (model.DigitalCamera): The CCD """ image = AcquireNoBackground(ccd, dfbkg) center_pxs = ((image.shape[1] / 2), (image.shape[0] / 2)) spot_pxs = FindSpot(image) tab_pxs = [a - b for a, b in zip(spot_pxs, center_pxs)] max_dim = int(max(abs(tab_pxs[0]), abs(tab_pxs[1]))) range_x = (ccd.resolution.range[0][0], ccd.resolution.range[1][0]) range_y = (ccd.resolution.range[0][1], ccd.resolution.range[1][1]) ccd.resolution.value = (sorted( (range_x[0], 2 * max_dim + FOV_MARGIN, range_x[1]))[1], sorted((range_y[0], 2 * max_dim + FOV_MARGIN, range_y[1]))[1]) ccd.binning.value = (1, 1)
def _DoCenterSpot(future, ccd, stage, escan, mx_steps, type, dfbkg): """ Iteratively acquires an optical image, finds the coordinates of the spot (center) and moves the stage to this position. Repeats until the found coordinates are at the center of the optical image or a maximum number of steps is reached. future (model.ProgressiveFuture): Progressive future provided by the wrapper ccd (model.DigitalCamera): The CCD stage (model.Actuator): The stage escan (model.Emitter): The e-beam scanner mx_steps (int): Maximum number of steps to reach the center type (*_MOVE or BEAM_SHIFT): Type of move in order to align dfbkg (model.DataFlow or None): If provided, will be used to start/stop the e-beam emmision (it must be the dataflow of se- or bs-detector) in order to do background subtraction. If None, no background subtraction is performed. returns (float or None): Final distance to the center (m) (2 floats): vector to the spot from the center (m, m) raises: CancelledError() if cancelled """ try: logging.debug("Aligning spot...") steps = 0 # Stop once spot is found on the center of the optical image dist = None while True: if future._spot_center_state == CANCELLED: raise CancelledError() # Or once max number of steps is reached if steps >= mx_steps: break # Wait to make sure no previous spot is detected image = AcquireNoBackground(ccd, dfbkg) try: spot_pxs = FindSpot(image) except LookupError: return None, None # Center of optical image pixelSize = image.metadata[model.MD_PIXEL_SIZE] center_pxs = (image.shape[1] / 2, image.shape[0] / 2) # Epsilon distance below which the lens is considered centered. The worse of: # * 1.5 pixels (because the CCD resolution cannot give us better) # * 1 µm (because that's the best resolution of our actuators) err_mrg = max(1.5 * pixelSize[0], 1e-06) # m tab_pxs = [a - b for a, b in zip(spot_pxs, center_pxs)] tab = (tab_pxs[0] * pixelSize[0], tab_pxs[1] * pixelSize[1]) logging.debug("Found spot @ %s px", spot_pxs) dist = math.hypot(*tab) # If we are already there, stop if dist <= err_mrg: break # Move to the found spot if type == OBJECTIVE_MOVE: f = stage.moveRel({"x": tab[0], "y":-tab[1]}) f.result() elif type == STAGE_MOVE: f = stage.moveRel({"x":-tab[0], "y": tab[1]}) f.result() else: escan.translation.value = (-tab_pxs[0], -tab_pxs[1]) steps += 1 # Update progress of the future future.set_progress(end=time.time() + estimateCenterTime(ccd.exposureTime.value, dist)) return dist, tab finally: with future._center_lock: if future._spot_center_state == CANCELLED: raise CancelledError() future._spot_center_state = FINISHED
def _DoAlignSpot(future, ccd, stage, escan, focus, type, dfbkg, rng_f): """ Adjusts settings until we have a clear and well focused optical spot image, detects the spot and manipulates the stage so as to move the spot center to the optical image center. If no spot alignment is achieved an exception is raised. future (model.ProgressiveFuture): Progressive future provided by the wrapper ccd (model.DigitalCamera): The CCD stage (model.Actuator): The stage escan (model.Emitter): The e-beam scanner focus (model.Actuator): The optical focus type (string): Type of move in order to align dfbkg (model.DataFlow): dataflow of se- or bs- detector rng_f (tuple of floats): range to apply Autofocus on if needed returns (float): Final distance to the center #m raises: CancelledError() if cancelled IOError """ init_binning = ccd.binning.value init_et = ccd.exposureTime.value init_cres = ccd.resolution.value init_scale = escan.scale.value init_eres = escan.resolution.value # TODO: allow to pass the precision as argument. As for the Delphi, we don't # need such an accuracy on the alignment (as it's just for twin stage calibration). # TODO: take logpath as argument, to store images later on logging.debug("Starting Spot alignment...") try: if future._task_state == CANCELLED: raise CancelledError() # Configure CCD and set ebeam to spot mode logging.debug("Configure CCD and set ebeam to spot mode...") ccd.binning.value = ccd.binning.clip((2, 2)) ccd.resolution.value = ccd.resolution.range[1] ccd.exposureTime.value = 0.3 escan.scale.value = (1, 1) escan.resolution.value = (1, 1) if future._task_state == CANCELLED: raise CancelledError() logging.debug("Adjust exposure time...") if dfbkg is None: # Long exposure time to compensate for no background subtraction ccd.exposureTime.value = 1.1 else: # TODO: all this code to decide whether to pick exposure 0.3 or 1.5? # => KISS! Use always 1s... or allow up to 5s? # Estimate noise and adjust exposure time based on "Rose criterion" image = AcquireNoBackground(ccd, dfbkg) snr = MeasureSNR(image) while snr < 5 and ccd.exposureTime.value < 1.5: ccd.exposureTime.value = ccd.exposureTime.value + 0.2 image = AcquireNoBackground(ccd, dfbkg) snr = MeasureSNR(image) logging.debug("Using exposure time of %g s", ccd.exposureTime.value) hqet = ccd.exposureTime.value # exposure time for high-quality (binning == 1x1) if ccd.binning.value == (2, 2): hqet *= 4 # To compensate for smaller binning logging.debug("Trying to find spot...") for i in range(3): if future._task_state == CANCELLED: raise CancelledError() if i == 0: future._centerspotf = CenterSpot(ccd, stage, escan, ROUGH_MOVE, type, dfbkg) dist, vector = future._centerspotf.result() elif i == 1: logging.debug("Spot not found, auto-focusing...") try: # When Autofocus set binning 8 if possible, and use exhaustive # method to be sure not to miss the spot. ccd.binning.value = ccd.binning.clip((8, 8)) future._autofocusf = autofocus.AutoFocus(ccd, None, focus, dfbkg, rng_focus=rng_f, method=MTD_EXHAUSTIVE) lens_pos, fm_level = future._autofocusf.result() # Update progress of the future future.set_progress(end=time.time() + estimateAlignmentTime(hqet, dist, 1)) except IOError as ex: logging.error("Autofocus on spot image failed: %s", ex) raise IOError('Spot alignment failure. AutoFocus failed.') logging.debug("Trying again to find spot...") future._centerspotf = CenterSpot(ccd, stage, escan, ROUGH_MOVE, type, dfbkg) dist, vector = future._centerspotf.result() elif i == 2: if dfbkg is not None: # In some case background subtraction goes wrong, and makes # things worse, so try without. logging.debug("Trying again to find spot, without background subtraction...") dfbkg = None future._centerspotf = CenterSpot(ccd, stage, escan, ROUGH_MOVE, type, dfbkg) dist, vector = future._centerspotf.result() if dist is not None: break else: raise IOError('Spot alignment failure. Spot not found') ccd.binning.value = (1, 1) ccd.exposureTime.value = ccd.exposureTime.clip(hqet) # Update progress of the future future.set_progress(end=time.time() + estimateAlignmentTime(hqet, dist, 1)) logging.debug("After rough alignment, spot center is at %s m", vector) # Limit FoV to save time logging.debug("Cropping FoV...") CropFoV(ccd, dfbkg) if future._task_state == CANCELLED: raise CancelledError() # Update progress of the future future.set_progress(end=time.time() + estimateAlignmentTime(hqet, dist, 0)) # Center spot if future._task_state == CANCELLED: raise CancelledError() logging.debug("Aligning spot...") future._centerspotf = CenterSpot(ccd, stage, escan, FINE_MOVE, type, dfbkg) dist, vector = future._centerspotf.result() if dist is None: raise IOError('Spot alignment failure. Cannot reach the center.') logging.info("After fine alignment, spot center is at %s m", vector) return dist, vector finally: ccd.binning.value = init_binning ccd.exposureTime.value = init_et ccd.resolution.value = init_cres escan.scale.value = init_scale escan.resolution.value = init_eres with future._alignment_lock: future._done.set() if future._task_state == CANCELLED: raise CancelledError() future._task_state = FINISHED