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
def main(args): """ Handles the command line arguments args is the list of arguments passed return (int): value to return to the OS as program exit code """ parser = argparse.ArgumentParser(description="Measure the needed SEM calibration") parser.add_argument("--move", dest="move", action='store_true', help="First to move to the standard location for Delphi calibration on the sample") parser.add_argument("--autofocus", "-f", dest="focus", action='store_true', help="Auto focus the SEM image before calibrating") options = parser.parse_args(args[1:]) try: escan = model.getComponent(role="e-beam") bsd = model.getComponent(role="bs-detector") # This moves the SEM stage precisely on the hole, as the calibration does it if options.move: semstage = model.getComponent(role="sem-stage") semstage.moveAbs(delphi.SHIFT_DETECTION).result() if options.focus: efocus = model.getComponent(role="ebeam-focus") efocus.moveAbs({"z": delphi.SEM_KNOWN_FOCUS}).result() f = autofocus.AutoFocus(bsd, escan, efocus) focus, fm_level = f.result() print("SEM focused @ %g m" % (focus,)) logging.debug("Starting Phenom SEM calibration...") blank_md = dict.fromkeys(delphi.MD_CALIB_SEM, (0, 0)) escan.updateMetadata(blank_md) # Compute spot shift percentage f = delphi.ScaleShiftFactor(bsd, escan, logpath="./") scale_shift = f.result() print("Spot shift = %s" % (scale_shift,)) # Compute HFW-related values f = delphi.HFWShiftFactor(bsd, escan, logpath="./") hfw_shift = f.result() print("HFW shift = %s" % (hfw_shift,)) # Compute resolution-related values f = delphi.ResolutionShiftFactor(bsd, escan, logpath="./") resa, resb = f.result() print("res A = %s, res B = %s" % (resa, resb)) # Final metadata, as understood by the phenom driver print("Calibration is:\n" "RESOLUTION_SLOPE: %s\n" "RESOLUTION_INTERCEPT: %s\n" "HFW_SLOPE: %s\n" "SPOT_SHIFT: %s\n" % (resa, resb, hfw_shift, scale_shift) ) calib_md = { model.MD_RESOLUTION_SLOPE: resa, model.MD_RESOLUTION_INTERCEPT: resb, model.MD_HFW_SLOPE: hfw_shift, model.MD_SPOT_SHIFT: scale_shift } escan.updateMetadata(calib_md) except Exception: logging.exception("Unexpected error while performing action.") return 127 return 0