def __init__(self, *args, **kwargs): super(DelphiStateController, self).__init__(*args, **kwargs) self._calibconf = get_calib_conf() # If starts with the sample fully loaded, check for the calibration now ch_pos = self._main_data.chamber.position if ch_pos.value["pressure"] == self._vacuum_pressure: # If it's loaded, the sample holdre is registered for sure, and the # calibration should have already been done. self._load_holder_calib() # Progress dialog for calibration self._dlg = None
def on_calib_done(self, future): """ Callback called when the calibration is finished (either successfully or cancelled) """ # bind button back to direct closure self.info_txt.SetLabel("Calibration of the sample holder ended") self.cancel_btn.Bind(wx.EVT_BUTTON, self.on_close) # Eject the sample holder self._main_data.chamberState.value = model.CHAMBER_VENTING try: shcalib = future.result(1) # timeout is just for safety except CancelledError: # hide progress bar (+ put pack estimated time) self.time_txt.SetLabel("Calibration cancelled.") self.cancel_btn.SetLabel("Close") self.gauge.Hide() self.Fit() return except Exception as e: # Suggest to the user to run the semi-manual calibration self.calib_future.cancel() self.time_txt.SetLabel( "Automatic calibration failed:\n" "%s\n\n" "Please follow the manual calibration procedure. \n" "Press Run to start." % (e, )) self.cancel_btn.SetLabel("Run") self.cancel_btn.Bind(wx.EVT_BUTTON, self.on_run_manual) self.Fit() return # Update the calibration file calibconf = get_calib_conf() calibconf.set_sh_calib(self._shid, *shcalib) # self.update_calibration_time(0) self.time_txt.SetLabel("Calibration completed.") # As the action is complete, rename "Cancel" to "Close" self.cancel_btn.SetLabel("Close")
def on_calib_done(self, future): """ Callback called when the calibration is finished (either successfully or cancelled) """ # bind button back to direct closure self.info_txt.SetLabel("Calibration of the sample holder ended") self.cancel_btn.Bind(wx.EVT_BUTTON, self.on_close) # Eject the sample holder self._main_data.chamberState.value = model.CHAMBER_VENTING try: shcalib = future.result(1) # timeout is just for safety except CancelledError: # hide progress bar (+ put pack estimated time) self.time_txt.SetLabel("Calibration cancelled.") self.cancel_btn.SetLabel("Close") self.gauge.Hide() self.Fit() return except Exception as e: # Suggest to the user to run the semi-manual calibration self.calib_future.cancel() self.time_txt.SetLabel("Automatic calibration failed:\n" "%s\n\n" "Please follow the manual calibration procedure. \n" "Press Run to start." % (e,)) self.cancel_btn.SetLabel("Run") self.cancel_btn.Bind(wx.EVT_BUTTON, self.on_run_manual) self.Fit() return # Update the calibration file calibconf = get_calib_conf() calibconf.set_sh_calib(self._shid, *shcalib) # self.update_calibration_time(0) self.time_txt.SetLabel("Calibration completed.") # As the action is complete, rename "Cancel" to "Close" self.cancel_btn.SetLabel("Close")
def man_calib(logpath): escan = None detector = None ccd = None # find components by their role for c in model.getComponents(): if c.role == "e-beam": escan = c elif c.role == "bs-detector": detector = c elif c.role == "ccd": ccd = c elif c.role == "sem-stage": sem_stage = c elif c.role == "align": opt_stage = c elif c.role == "ebeam-focus": ebeam_focus = c elif c.role == "overview-focus": navcam_focus = c elif c.role == "focus": focus = c elif c.role == "overview-ccd": overview_ccd = c elif c.role == "chamber": chamber = c if not all([escan, detector, ccd]): logging.error("Failed to find all the components") raise KeyError("Not all components found") hw_settings = aligndelphi.list_hw_settings(escan, ccd) try: # Get pressure values pressures = chamber.axes["pressure"].choices vacuum_pressure = min(pressures.keys()) vented_pressure = max(pressures.keys()) if overview_ccd: for p, pn in pressures.items(): if pn == "overview": overview_pressure = p break else: raise IOError("Failed to find the overview pressure in %s" % (pressures,)) calibconf = get_calib_conf() shid, sht = chamber.sampleHolder.value calib_values = calibconf.get_sh_calib(shid) if calib_values is None: first_hole = second_hole = offset = resa = resb = hfwa = scaleshift = (0, 0) scaling = iscale = iscale_xy = (1, 1) rotation = irot = ishear = 0 hole_focus = aligndelphi.SEM_KNOWN_FOCUS opt_focus = aligndelphi.OPTICAL_KNOWN_FOCUS print_col(ANSI_RED, "Calibration values missing! All the steps will be performed anyway...") force_calib = True else: first_hole, second_hole, hole_focus, opt_focus, offset, scaling, rotation, iscale, irot, iscale_xy, ishear, resa, resb, hfwa, scaleshift = calib_values force_calib = False print_col(ANSI_CYAN, "**Delphi Manual Calibration steps**\n" "1.Sample holder hole detection\n" " Current values: 1st hole: " + str(first_hole) + "\n" " 2st hole: " + str(second_hole) + "\n" " hole focus: " + str(hole_focus) + "\n" "2.SEM image calibration\n" " Current values: resolution-a: " + str(resa) + "\n" " resolution-b: " + str(resb) + "\n" " hfw-a: " + str(hfwa) + "\n" " spot shift: " + str(scaleshift) + "\n" "3.Twin stage calibration\n" " Current values: offset: " + str(offset) + "\n" " scaling: " + str(scaling) + "\n" " rotation: " + str(rotation) + "\n" " optical focus: " + str(opt_focus) + "\n" "4.Fine alignment\n" " Current values: scale: " + str(iscale) + "\n" " rotation: " + str(irot) + "\n" " scale-xy: " + str(iscale_xy) + "\n" " shear: " + str(ishear)) print_col(ANSI_YELLOW, "Note that you should not perform any stage move during the process.\n" "Instead, you may zoom in/out while focusing.") print_col(ANSI_BLACK, "Now initializing, please wait...") # Move to the overview position first f = chamber.moveAbs({"pressure": overview_pressure}) f.result() # Reference the (optical) stage f = opt_stage.reference({"x", "y"}) f.result() f = focus.reference({"z"}) f.result() # SEM stage to (0,0) f = sem_stage.moveAbs({"x": 0, "y": 0}) f.result() # Calculate offset approximation try: f = aligndelphi.LensAlignment(overview_ccd, sem_stage, logpath) position = f.result() except IOError as ex: if not force_calib: position = (offset[0] * scaling[0], offset[1] * scaling[1]) else: position = (0, 0) logging.warning("Failed to locate the optical lens (%s), will used previous value %s", ex, position) # Just to check if move makes sense f = sem_stage.moveAbs({"x": position[0], "y": position[1]}) f.result() # Move to SEM f = chamber.moveAbs({"pressure": vacuum_pressure}) f.result() # Set basic e-beam settings escan.spotSize.value = 2.7 escan.accelVoltage.value = 5300 # V # Without automatic blanker, the background subtraction doesn't work if (model.hasVA(escan, "blanker") and # For simulator None in escan.blanker.choices and escan.blanker.value is not None): logging.warning("Blanker set back to automatic") escan.blanker.value = None # Detect the holes/markers of the sample holder while True: ans = "Y" if force_calib else None while ans not in YES_NO_CHARS: ans = input_col(ANSI_MAGENTA, "Do you want to execute the sample holder hole detection? [Y/n]") if ans in YES_CHARS: # Move Phenom sample stage next to expected hole position sem_stage.moveAbsSync(aligndelphi.SHIFT_DETECTION) ebeam_focus.moveAbsSync({"z": hole_focus}) # Set the FoV to almost 2mm escan.horizontalFoV.value = escan.horizontalFoV.range[1] input_col(ANSI_BLUE, "Please turn on the SEM stream and focus the SEM image. Then turn off the stream and press Enter...") print_col(ANSI_BLACK,"Trying to detect the holes/markers, please wait...") try: hole_detectionf = aligndelphi.HoleDetection(detector, escan, sem_stage, ebeam_focus, manual=True, logpath=logpath) new_first_hole, new_second_hole, new_hole_focus = hole_detectionf.result() print_col(ANSI_CYAN, "Values computed: 1st hole: " + str(new_first_hole) + "\n" " 2st hole: " + str(new_second_hole) + "\n" " hole focus: " + str(new_hole_focus)) ans = "Y" if force_calib else None while ans not in YES_NO_CHARS: ans = input_col(ANSI_MAGENTA, "Do you want to update the calibration file with these values? [Y/n]") if ans in YES_CHARS: first_hole, second_hole, hole_focus = new_first_hole, new_second_hole, new_hole_focus calibconf.set_sh_calib(shid, first_hole, second_hole, hole_focus, opt_focus, offset, scaling, rotation, iscale, irot, iscale_xy, ishear, resa, resb, hfwa, scaleshift) print_col(ANSI_BLACK, "Calibration file is updated.") break except IOError: print_col(ANSI_RED, "Sample holder hole detection failed.") else: break while True: ans = "Y" if force_calib else None while ans not in YES_NO_CHARS: ans = input_col(ANSI_MAGENTA, "Do you want to execute the SEM image calibration? [Y/n]") if ans in YES_CHARS: # Resetting shift parameters, to not take them into account during calib blank_md = dict.fromkeys(aligndelphi.MD_CALIB_SEM, (0, 0)) escan.updateMetadata(blank_md) # We measure the shift in the area just behind the hole where there # are always some features plus the edge of the sample carrier. For # that reason we use the focus measured in the hole detection step sem_stage.moveAbsSync(aligndelphi.SHIFT_DETECTION) ebeam_focus.moveAbsSync({"z": hole_focus}) try: # Compute spot shift percentage print_col(ANSI_BLACK, "Spot shift measurement in progress, please wait...") f = aligndelphi.ScaleShiftFactor(detector, escan, logpath) new_scaleshift = f.result() # Compute resolution-related values. print_col(ANSI_BLACK, "Calculating resolution shift, please wait...") resolution_shiftf = aligndelphi.ResolutionShiftFactor(detector, escan, logpath) new_resa, new_resb = resolution_shiftf.result() # Compute HFW-related values print_col(ANSI_BLACK, "Calculating HFW shift, please wait...") hfw_shiftf = aligndelphi.HFWShiftFactor(detector, escan, logpath) new_hfwa = hfw_shiftf.result() print_col(ANSI_CYAN, "Values computed: resolution-a: " + str(new_resa) + "\n" " resolution-b: " + str(new_resb) + "\n" " hfw-a: " + str(new_hfwa) + "\n" " spot shift: " + str(new_scaleshift)) ans = "Y" if force_calib else None while ans not in YES_NO_CHARS: ans = input_col(ANSI_MAGENTA, "Do you want to update the calibration file with these values? [Y/n]") if ans in YES_CHARS: resa, resb, hfwa, scaleshift = new_resa, new_resb, new_hfwa, new_scaleshift calibconf.set_sh_calib(shid, first_hole, second_hole, hole_focus, opt_focus, offset, scaling, rotation, iscale, irot, iscale_xy, ishear, resa, resb, hfwa, scaleshift) print_col(ANSI_BLACK, "Calibration file is updated.") break except IOError: print_col(ANSI_RED, "SEM image calibration failed.") else: break # Update the SEM metadata to have the spots already at corrected place escan.updateMetadata({ model.MD_RESOLUTION_SLOPE: resa, model.MD_RESOLUTION_INTERCEPT: resb, model.MD_HFW_SLOPE: hfwa, model.MD_SPOT_SHIFT: scaleshift }) f = sem_stage.moveAbs({"x": position[0], "y": position[1]}) f.result() f = opt_stage.moveAbs({"x": 0, "y": 0}) f.result() if hole_focus is not None: good_focus = hole_focus - aligndelphi.GOOD_FOCUS_OFFSET else: good_focus = aligndelphi.SEM_KNOWN_FOCUS - aligndelphi.GOOD_FOCUS_OFFSET f = ebeam_focus.moveAbs({"z": good_focus}) f.result() # Set min fov # We want to be as close as possible to the center when we are zoomed in escan.horizontalFoV.value = escan.horizontalFoV.range[0] pure_offset = None # Start with the best optical focus known so far f = focus.moveAbs({"z": opt_focus}) f.result() while True: ans = "Y" if force_calib else None while ans not in YES_NO_CHARS: ans = input_col(ANSI_MAGENTA, "Do you want to execute the twin stage calibration? [Y/n]") if ans in YES_CHARS: # Configure CCD and e-beam to write CL spots ccd.binning.value = ccd.binning.clip((4, 4)) ccd.resolution.value = ccd.resolution.range[1] ccd.exposureTime.value = 900e-03 escan.scale.value = (1, 1) escan.resolution.value = (1, 1) escan.translation.value = (0, 0) if not escan.rotation.readonly: escan.rotation.value = 0 escan.shift.value = (0, 0) escan.dwellTime.value = 5e-06 detector.data.subscribe(_discard_data) print_col(ANSI_BLUE, "Please turn on the Optical stream, set Power to 0 Watt " "and focus the image so you have a clearly visible spot.\n" "Use the up and down arrows or the mouse to move the " "optical focus and right and left arrows to move the SEM focus. " "Then turn off the stream and press Enter...") if not force_calib: print_col(ANSI_YELLOW, "If you cannot see the whole source background (bright circle) " "you may try to move to the already known offset position. \n" "To do this press the R key at any moment and use I to go back " "to the initial position.") rollback_pos = (offset[0] * scaling[0], offset[1] * scaling[1]) else: rollback_pos = None ar = ArrowFocus(sem_stage, focus, ebeam_focus, ccd.depthOfField.value, 10e-6) ar.focusByArrow(rollback_pos) detector.data.unsubscribe(_discard_data) print_col(ANSI_BLACK, "Twin stage calibration starting, please wait...") try: # TODO: the first point (at 0,0) isn't different from the next 4 points, # excepted it might be a little harder to focus. # => use the same code for all of them align_offsetf = aligndelphi.AlignAndOffset(ccd, detector, escan, sem_stage, opt_stage, focus, logpath) align_offset = align_offsetf.result() new_opt_focus = focus.position.value.get('z') # If the offset is large, it can prevent the SEM stage to follow # the optical stage. If it's really large (eg > 1mm) it will # even prevent from going to the calibration locations. # So warn about this, as soon as we detect it. It could be # caused either due to a mistake in the offset detection, or # because the reference switch of the optical axis is not # centered (enough) on the axis. In such case, a technician # should open the sample holder and move the reference switch. # Alternatively, we could try to be more clever and support # the range of the tmcm axes to be defined per sample # holder, and set some asymmetric range (to reflect the fact # that 0 is not at the center). for a, trans in zip(("x", "y"), align_offset): # SEM pos = Opt pos + offset rng_sem = sem_stage.axes[a].range rng_opt = opt_stage.axes[a].range if (rng_opt[0] + trans < rng_sem[0] or rng_opt[1] + trans > rng_sem[1]): logging.info("Stage align offset = %s, which could cause " "moves on the SEM stage out of range (on axis %s)", align_offset, a) input_col(ANSI_RED, "Twin stage offset on axis %s is %g mm, which could cause moves out of range.\n" "Check that the reference switch in the sample holder is properly at the center." % (a, trans * 1e3)) def ask_user_to_focus(n): detector.data.subscribe(_discard_data) input_col(ANSI_BLUE, "About to calculate rotation and scaling (%d/4). " % (n + 1,) + "Please turn on the Optical stream, " "set Power to 0 Watt and focus the image using the mouse " "so you have a clearly visible spot. \n" "If you do not see a spot nor the source background, " "move the sem-stage from the command line by steps of 200um " "in x and y until you can see the source background at the center. \n" "Then turn off the stream and press Enter...") # TODO: use ArrowFocus() too? print_col(ANSI_BLACK, "Calculating rotation and scaling (%d/4), please wait..." % (n + 1,)) detector.data.unsubscribe(_discard_data) f = aligndelphi.RotationAndScaling(ccd, detector, escan, sem_stage, opt_stage, focus, align_offset, manual=ask_user_to_focus, logpath=logpath) acc_offset, new_rotation, new_scaling = f.result() # Offset is divided by scaling, since Convert Stage applies scaling # also in the given offset pure_offset = acc_offset new_offset = ((acc_offset[0] / new_scaling[0]), (acc_offset[1] / new_scaling[1])) print_col(ANSI_CYAN, "Values computed: offset: " + str(new_offset) + "\n" " scaling: " + str(new_scaling) + "\n" " rotation: " + str(new_rotation) + "\n" " optical focus: " + str(new_opt_focus)) ans = "Y" if force_calib else None while ans not in YES_NO_CHARS: ans = input_col(ANSI_MAGENTA, "Do you want to update the calibration file with these values? [Y/n]") if ans in YES_CHARS: offset, scaling, rotation, opt_focus = new_offset, new_scaling, new_rotation, new_opt_focus calibconf.set_sh_calib(shid, first_hole, second_hole, hole_focus, opt_focus, offset, scaling, rotation, iscale, irot, iscale_xy, ishear, resa, resb, hfwa, scaleshift) print_col(ANSI_BLACK, "Calibration file is updated.") break except IOError: print_col(ANSI_RED, "Twin stage calibration failed.") else: break while True: ans = "Y" if force_calib else None while ans not in YES_NO_CHARS: ans = input_col(ANSI_MAGENTA, "Do you want to execute the fine alignment? [Y/n]") if ans in YES_CHARS: # Return to the center so fine alignment can be executed just after calibration f = opt_stage.moveAbs({"x": 0, "y": 0}) f.result() if pure_offset is not None: f = sem_stage.moveAbs({"x":pure_offset[0], "y":pure_offset[1]}) f.result() elif offset is not None: f = sem_stage.moveAbs({"x":offset[0] * scaling[0], "y":offset[1] * scaling[1]}) f.result() else: f = sem_stage.moveAbs({"x":position[0], "y":position[1]}) f.result() f = focus.moveAbs({"z": opt_focus}) f.result() f = ebeam_focus.moveAbs({"z": good_focus}) f.result() # Run the optical fine alignment # TODO: reuse the exposure time # Configure e-beam to write CL spots escan.horizontalFoV.value = escan.horizontalFoV.range[0] escan.scale.value = (1, 1) escan.resolution.value = (1, 1) escan.translation.value = (0, 0) if not escan.rotation.readonly: escan.rotation.value = 0 escan.shift.value = (0, 0) escan.dwellTime.value = 5e-06 detector.data.subscribe(_discard_data) print_col(ANSI_BLUE, "Please turn on the Optical stream, set Power to 0 Watt " "and focus the image so you have a clearly visible spot.\n" "Use the up and down arrows or the mouse to move the " "optical focus and right and left arrows to move the SEM focus. " "Then turn off the stream and press Enter...") ar = ArrowFocus(sem_stage, focus, ebeam_focus, ccd.depthOfField.value, 10e-6) ar.focusByArrow() detector.data.unsubscribe(_discard_data) print_col(ANSI_BLACK, "Fine alignment in progress, please wait...") # restore CCD settings (as the GUI/user might have changed them) ccd.binning.value = (1, 1) ccd.resolution.value = ccd.resolution.range[1] ccd.exposureTime.value = 900e-03 # Center (roughly) the spot on the CCD f = spot.CenterSpot(ccd, sem_stage, escan, spot.ROUGH_MOVE, spot.STAGE_MOVE, detector.data) dist, vect = f.result() if dist is None: logging.warning("Failed to find a spot, twin stage calibration might have failed") try: escan.horizontalFoV.value = 80e-06 f = align.FindOverlay((4, 4), 0.5, # s, dwell time 10e-06, # m, maximum difference allowed escan, ccd, detector, skew=True, bgsub=True) trans_val, cor_md = f.result() trans_md, skew_md = cor_md new_iscale = trans_md[model.MD_PIXEL_SIZE_COR] new_irot = -trans_md[model.MD_ROTATION_COR] % (2 * math.pi) new_ishear = skew_md[model.MD_SHEAR_COR] new_iscale_xy = skew_md[model.MD_PIXEL_SIZE_COR] print_col(ANSI_CYAN, "Values computed: scale: " + str(new_iscale) + "\n" " rotation: " + str(new_irot) + "\n" " scale-xy: " + str(new_iscale_xy) + "\n" " shear: " + str(new_ishear)) ans = "Y" if force_calib else None while ans not in YES_NO_CHARS: ans = input_col(ANSI_MAGENTA, "Do you want to update the calibration file with these values? [Y/n]") if ans in YES_CHARS: iscale, irot, iscale_xy, ishear = new_iscale, new_irot, new_iscale_xy, new_ishear calibconf.set_sh_calib(shid, first_hole, second_hole, hole_focus, opt_focus, offset, scaling, rotation, iscale, irot, iscale_xy, ishear, resa, resb, hfwa, scaleshift) print_col(ANSI_BLACK, "Calibration file is updated.") break except ValueError: print_col(ANSI_RED, "Fine alignment failed.") else: break except Exception: logging.exception("Unexpected failure during calibration") finally: print_col(ANSI_BLACK, "Calibration ended, now ejecting sample, please wait...") # Eject the sample holder f = chamber.moveAbs({"pressure": vented_pressure}) aligndelphi.restore_hw_settings(escan, ccd, hw_settings) # Store the final version of the calibration file in the log folder try: shutil.copy(calibconf.file_path, logpath) except Exception: logging.info("Failed to log calibration file", exc_info=True) ans = input_col(ANSI_MAGENTA, "Press Enter to close") f.result()
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 """ try: escan = None detector = None ccd = None # find components by their role for c in model.getComponents(): if c.role == "e-beam": escan = c elif c.role == "bs-detector": detector = c elif c.role == "ccd": ccd = c elif c.role == "sem-stage": sem_stage = c elif c.role == "align": opt_stage = c elif c.role == "ebeam-focus": ebeam_focus = c elif c.role == "overview-focus": navcam_focus = c elif c.role == "focus": focus = c elif c.role == "overview-ccd": overview_ccd = c elif c.role == "chamber": chamber = c if not all([escan, detector, ccd]): logging.error("Failed to find all the components") raise KeyError("Not all components found") # Get pressure values pressures = chamber.axes["pressure"].choices vacuum_pressure = min(pressures.keys()) vented_pressure = max(pressures.keys()) if overview_ccd: for p, pn in pressures.items(): if pn == "overview": overview_pressure = p break else: raise IOError("Failed to find the overview pressure in %s" % (pressures,)) calibconf = get_calib_conf() shid, sht = chamber.sampleHolder.value calib_values = calibconf.get_sh_calib(shid) if calib_values is None: first_hole = second_hole = offset = resa = resb = hfwa = spotshift = (0, 0) scaling = iscale = iscale_xy = (1, 1) hole_focus = rotation = irot = ishear = 0 print "\033[1;31mCalibration values missing! All the steps will be performed anyway...\033[1;m" force_calib = True else: first_hole, second_hole, hole_focus, offset, scaling, rotation, iscale, irot, iscale_xy, ishear, resa, resb, hfwa, spotshift = calib_values force_calib = False print '\033[1;36m' print "**Delphi Manual Calibration steps**" print "1.Sample holder hole detection" print " Current values: 1st hole: " + str(first_hole) print " 2st hole: " + str(second_hole) print " hole focus: " + str(hole_focus) print "2.Twin stage calibration" print " Current values: offset: " + str(offset) print " scaling: " + str(scaling) print " rotation: " + str(rotation) print "3.SEM image calibration" print " Current values: resolution-a: " + str(resa) print " resolution-b: " + str(resb) print " hfw-a: " + str(hfwa) print " spot shift: " + str(spotshift) print "4.Fine alignment" print " Current values: scale: " + str(iscale) print " rotation: " + str(irot) print " scale-xy: " + str(iscale_xy) print " shear: " + str(ishear) print '\033[1;m' print "\033[1;33mNote that you should not perform any stage move during the process. \nInstead, you may zoom in/out while focusing.\033[1;m" print "\033[1;30mNow initializing, please wait...\033[1;m" # Move to the overview position first f = chamber.moveAbs({"pressure": overview_pressure}) f.result() # Reference the (optical) stage f = opt_stage.reference({"x", "y"}) f.result() f = focus.reference({"z"}) f.result() # SEM stage to (0,0) f = sem_stage.moveAbs({"x": 0, "y": 0}) f.result() # Calculate offset approximation try: f = aligndelphi.LensAlignment(overview_ccd, sem_stage) position = f.result() except Exception: raise IOError("Lens alignment failed.") # Just to check if move makes sense f = sem_stage.moveAbs({"x": position[0], "y": position[1]}) f.result() # Move to SEM f = chamber.moveAbs({"pressure": vacuum_pressure}) f.result() while True: ans = None while ans not in YES_NO_CHARS: msg = "\033[1;35mDo you want to execute the sample holder hole detection? [Y/n]\033[1;m" ans = raw_input(msg) if ans in YES_CHARS or force_calib: # Compute stage calibration values # Detect the holes/markers of the sample holder # Move Phenom sample stage to expected hole position f = sem_stage.moveAbs(aligndelphi.EXPECTED_HOLES[0]) f.result() # Set the FoV to almost 2mm escan.horizontalFoV.value = escan.horizontalFoV.range[1] msg = "\033[1;34mPlease turn on the SEM stream and focus the SEM image. Then turn off the stream and press Enter ...\033[1;m" raw_input(msg) print "\033[1;30mTrying to detect the holes/markers, please wait...\033[1;m" try: hole_detectionf = aligndelphi.HoleDetection(detector, escan, sem_stage, ebeam_focus, known_focus=None, manual=True) new_first_hole, new_second_hole, new_hole_focus = hole_detectionf.result() new_hole_focus = ebeam_focus.position.value.get('z') print '\033[1;36m' print "Values computed: 1st hole: " + str(new_first_hole) print " 2st hole: " + str(new_second_hole) print " hole focus: " + str(new_hole_focus) print '\033[1;m' ans = None while ans not in YES_NO_CHARS: msg = "\033[1;35mDo you want to update the calibration file with these values? [Y/n]\033[1;m" ans = raw_input(msg) if ans in YES_CHARS or force_calib: first_hole, second_hole, hole_focus = new_first_hole, new_second_hole, new_hole_focus calibconf.set_sh_calib(shid, first_hole, second_hole, hole_focus, offset, scaling, rotation, iscale, irot, iscale_xy, ishear, resa, resb, hfwa, spotshift) break except IOError: print "\033[1;31mSample holder hole detection failed.\033[1;m" else: break f = sem_stage.moveAbs({"x":position[0], "y":position[1]}) f.result() f = opt_stage.moveAbs({"x": 0, "y": 0}) f.result() if hole_focus is not None: f = ebeam_focus.moveAbs({"z": hole_focus}) f.result() # Set min fov # We want to be as close as possible to the center when we are zoomed in escan.horizontalFoV.value = escan.horizontalFoV.range[0] pure_offset = None center_focus = None while True: ans = None while ans not in YES_NO_CHARS: msg = "\033[1;35mDo you want to execute the twin stage calibration? [Y/n]\033[1;m" ans = raw_input(msg) if ans in YES_CHARS or force_calib: # Configure CCD and e-beam to write CL spots ccd.binning.value = (1, 1) ccd.resolution.value = ccd.resolution.range[1] ccd.exposureTime.value = 900e-03 escan.scale.value = (1, 1) escan.resolution.value = (1, 1) escan.translation.value = (0, 0) escan.rotation.value = 0 escan.shift.value = (0, 0) escan.dwellTime.value = 5e-06 detector.data.subscribe(_discard_data) print "\033[1;34mPlease turn on the Optical stream, set Power to 0 Watt and focus the image so you have a clearly visible spot.\033[1;m" print "\033[1;34mUse the up and down arrows or the mouse to move the optical focus and right and left arrows to move the SEM focus. Then turn off the stream and press Enter ...\033[1;m" if not force_calib: print "\033[1;33mIf you cannot see the whole source background (bright circle) you may try to move to the already known offset position. \nTo do this press the R key at any moment.\033[1;m" ar = ArrowFocus(sem_stage, focus, ebeam_focus, ccd.depthOfField.value, escan.depthOfField.value) ar.focusByArrow(rollback_position=(offset[0] * scaling[0], offset[1] * scaling[1])) print "\033[1;30mFine alignment in progress, please wait...\033[1;m" detector.data.unsubscribe(_discard_data) try: align_offsetf = aligndelphi.AlignAndOffset(ccd, detector, escan, sem_stage, opt_stage, focus) align_offset = align_offsetf.result() center_focus = focus.position.value.get('z') rotation_scalingf = aligndelphi.RotationAndScaling(ccd, detector, escan, sem_stage, opt_stage, focus, align_offset, manual=True) acc_offset, new_rotation, new_scaling = rotation_scalingf.result() # Offset is divided by scaling, since Convert Stage applies scaling # also in the given offset pure_offset = acc_offset new_offset = ((acc_offset[0] / new_scaling[0]), (acc_offset[1] / new_scaling[1])) print '\033[1;36m' print "Values computed: offset: " + str(new_offset) print " scaling: " + str(new_scaling) print " rotation: " + str(new_rotation) print '\033[1;m' ans = None while ans not in YES_NO_CHARS: msg = "\033[1;35mDo you want to update the calibration file with these values? [Y/n]\033[1;m" ans = raw_input(msg) if ans in YES_CHARS or force_calib: offset, scaling, rotation = new_offset, new_scaling, new_rotation calibconf.set_sh_calib(shid, first_hole, second_hole, hole_focus, offset, scaling, rotation, iscale, irot, iscale_xy, ishear, resa, resb, hfwa, spotshift) break except IOError: print "\033[1;31mTwin stage calibration failed.\033[1;m" else: break while True: ans = None while ans not in YES_NO_CHARS: msg = "\033[1;35mDo you want to execute the SEM image calibration? [Y/n]\033[1;m" ans = raw_input(msg) if ans in YES_CHARS or force_calib: f = opt_stage.moveAbs({"x": 0, "y": 0}) f.result() if pure_offset is not None: f = sem_stage.moveAbs({"x":pure_offset[0], "y":pure_offset[1]}) f.result() elif offset is not None: f = sem_stage.moveAbs({"x":offset[0] * scaling[0], "y":offset[1] * scaling[1]}) f.result() else: f = sem_stage.moveAbs({"x":position[0], "y":position[1]}) f.result() if center_focus is not None: f = focus.moveAbs({"z": center_focus}) f.result() try: # Compute spot shift percentage # Configure CCD and e-beam to write CL spots ccd.binning.value = (1, 1) ccd.resolution.value = ccd.resolution.range[1] ccd.exposureTime.value = 900e-03 escan.scale.value = (1, 1) escan.resolution.value = (1, 1) escan.translation.value = (0, 0) escan.rotation.value = 0 escan.shift.value = (0, 0) escan.dwellTime.value = 5e-06 detector.data.subscribe(_discard_data) print "\033[1;34mPlease turn on the Optical stream, set Power to 0 Watt and focus the image so you have a clearly visible spot.\033[1;m" print "\033[1;34mUse the up and down arrows or the mouse to move the optical focus and right and left arrows to move the SEM focus. Then turn off the stream and press Enter ...\033[1;m" ar = ArrowFocus(sem_stage, focus, ebeam_focus, ccd.depthOfField.value, escan.depthOfField.value) ar.focusByArrow() print "\033[1;30mFine alignment in progress, please wait...\033[1;m" detector.data.unsubscribe(_discard_data) spot_shiftf = aligndelphi.SpotShiftFactor(ccd, detector, escan, focus) new_spotshift = spot_shiftf.result() print "\033[1;30mCalculating resolution and HFW shift, please wait...\033[1;m" # Compute resolution-related values resolution_shiftf = aligndelphi.ResolutionShiftFactor(detector, escan, sem_stage, ebeam_focus, hole_focus) new_resa, new_resb = resolution_shiftf.result() # Compute HFW-related values hfw_shiftf = aligndelphi.HFWShiftFactor(detector, escan, sem_stage, ebeam_focus, hole_focus) new_hfwa = hfw_shiftf.result() print '\033[1;36m' print "Values computed: resolution-a: " + str(new_resa) print " resolution-b: " + str(new_resb) print " hfw-a: " + str(new_hfwa) print " spot shift: " + str(new_spotshift) print '\033[1;m' ans = None while ans not in YES_NO_CHARS: msg = "\033[1;35mDo you want to update the calibration file with these values? [Y/n]\033[1;m" ans = raw_input(msg) if ans in YES_CHARS or force_calib: resa, resb, hfwa, spotshift = new_resa, new_resb, new_hfwa, new_spotshift calibconf.set_sh_calib(shid, first_hole, second_hole, hole_focus, offset, scaling, rotation, iscale, irot, iscale_xy, ishear, resa, resb, hfwa, spotshift) break except IOError: print "\033[1;31mSEM image calibration failed.\033[1;m" else: break while True: ans = None while ans not in YES_NO_CHARS: msg = "\033[1;35mDo you want to execute the fine alignment? [Y/n]\033[1;m" ans = raw_input(msg) if ans in YES_CHARS or force_calib: # Return to the center so fine alignment can be executed just after calibration f = opt_stage.moveAbs({"x": 0, "y": 0}) f.result() if pure_offset is not None: f = sem_stage.moveAbs({"x":pure_offset[0], "y":pure_offset[1]}) f.result() elif offset is not None: f = sem_stage.moveAbs({"x":offset[0] * scaling[0], "y":offset[1] * scaling[1]}) f.result() else: f = sem_stage.moveAbs({"x":position[0], "y":position[1]}) f.result() if center_focus is not None: f = focus.moveAbs({"z": center_focus}) f.result() # Run the optical fine alignment # TODO: reuse the exposure time # Configure CCD and e-beam to write CL spots ccd.binning.value = (1, 1) ccd.resolution.value = ccd.resolution.range[1] ccd.exposureTime.value = 900e-03 escan.horizontalFoV.value = escan.horizontalFoV.range[0] escan.scale.value = (1, 1) escan.resolution.value = (1, 1) escan.translation.value = (0, 0) escan.rotation.value = 0 escan.shift.value = (0, 0) escan.dwellTime.value = 5e-06 detector.data.subscribe(_discard_data) print "\033[1;34mPlease turn on the Optical stream, set Power to 0 Watt and focus the image so you have a clearly visible spot.\033[1;m" print "\033[1;34mUse the up and down arrows or the mouse to move the optical focus and right and left arrows to move the SEM focus. Then turn off the stream and press Enter ...\033[1;m" ar = ArrowFocus(sem_stage, focus, ebeam_focus, ccd.depthOfField.value, escan.depthOfField.value) ar.focusByArrow() print "\033[1;30mFine alignment in progress, please wait...\033[1;m" detector.data.unsubscribe(_discard_data) try: escan.horizontalFoV.value = 80e-06 f = align.FindOverlay((4, 4), 0.5, # s, dwell time 10e-06, # m, maximum difference allowed escan, ccd, detector, skew=True, bgsub=True) trans_val, cor_md = f.result() trans_md, skew_md = cor_md new_iscale = trans_md[model.MD_PIXEL_SIZE_COR] new_irot = -trans_md[model.MD_ROTATION_COR] % (2 * math.pi) new_ishear = skew_md[model.MD_SHEAR_COR] new_iscale_xy = skew_md[model.MD_PIXEL_SIZE_COR] print '\033[1;36m' print "Values computed: scale: " + str(new_iscale) print " rotation: " + str(new_irot) print " scale-xy: " + str(new_iscale_xy) print " shear: " + str(new_ishear) print '\033[1;m' ans = None while ans not in YES_NO_CHARS: msg = "\033[1;35mDo you want to update the calibration file with these values? [Y/n]\033[1;m" ans = raw_input(msg) if ans in YES_CHARS or force_calib: iscale, irot, iscale_xy, ishear = new_iscale, new_irot, new_iscale_xy, new_ishear calibconf.set_sh_calib(shid, first_hole, second_hole, hole_focus, offset, scaling, rotation, iscale, irot, iscale_xy, ishear, resa, resb, hfwa, spotshift) break except ValueError: print "\033[1;31mFine alignment failed.\033[1;m" else: break # Update calibration file print "\033[1;30mUpdating calibration file is done, now ejecting, please wait...\033[1;m" except KeyboardInterrupt: logging.warning("Manual calibration procedure was cancelled.") except: logging.exception("Unexpected error while performing action.") return 127 finally: # Eject the sample holder f = chamber.moveAbs({"pressure": vented_pressure}) f.result() return 0
def man_calib(logpath): escan = None detector = None ccd = None # find components by their role for c in model.getComponents(): if c.role == "e-beam": escan = c elif c.role == "bs-detector": detector = c elif c.role == "ccd": ccd = c elif c.role == "sem-stage": sem_stage = c elif c.role == "align": opt_stage = c elif c.role == "ebeam-focus": ebeam_focus = c elif c.role == "overview-focus": navcam_focus = c elif c.role == "focus": focus = c elif c.role == "overview-ccd": overview_ccd = c elif c.role == "chamber": chamber = c if not all([escan, detector, ccd]): logging.error("Failed to find all the components") raise KeyError("Not all components found") hw_settings = aligndelphi.list_hw_settings(escan, ccd) try: # Get pressure values pressures = chamber.axes["pressure"].choices vacuum_pressure = min(pressures.keys()) vented_pressure = max(pressures.keys()) if overview_ccd: for p, pn in pressures.items(): if pn == "overview": overview_pressure = p break else: raise IOError("Failed to find the overview pressure in %s" % (pressures, )) calibconf = get_calib_conf() shid, sht = chamber.sampleHolder.value calib_values = calibconf.get_sh_calib(shid) if calib_values is None: first_hole = second_hole = offset = resa = resb = hfwa = scaleshift = ( 0, 0) scaling = iscale = iscale_xy = (1, 1) rotation = irot = ishear = 0 hole_focus = aligndelphi.SEM_KNOWN_FOCUS opt_focus = aligndelphi.OPTICAL_KNOWN_FOCUS print "\033[1;31mCalibration values missing! All the steps will be performed anyway...\033[1;m" force_calib = True else: first_hole, second_hole, hole_focus, opt_focus, offset, scaling, rotation, iscale, irot, iscale_xy, ishear, resa, resb, hfwa, scaleshift = calib_values force_calib = False print "\033[1;36m" print "**Delphi Manual Calibration steps**" print "1.Sample holder hole detection" print " Current values: 1st hole: " + str(first_hole) print " 2st hole: " + str(second_hole) print " hole focus: " + str(hole_focus) print "2.Twin stage calibration" print " Current values: offset: " + str(offset) print " scaling: " + str(scaling) print " rotation: " + str(rotation) print " optical focus: " + str(opt_focus) print "3.Fine alignment" print " Current values: scale: " + str(iscale) print " rotation: " + str(irot) print " scale-xy: " + str(iscale_xy) print " shear: " + str(ishear) print "4.SEM image calibration" print " Current values: resolution-a: " + str(resa) print " resolution-b: " + str(resb) print " hfw-a: " + str(hfwa) print " spot shift: " + str(scaleshift) print '\033[1;m' print "\033[1;33mNote that you should not perform any stage move during the process. \nInstead, you may zoom in/out while focusing.\033[1;m" print "\033[1;30mNow initializing, please wait...\033[1;m" # Move to the overview position first f = chamber.moveAbs({"pressure": overview_pressure}) f.result() # Reference the (optical) stage f = opt_stage.reference({"x", "y"}) f.result() f = focus.reference({"z"}) f.result() # SEM stage to (0,0) f = sem_stage.moveAbs({"x": 0, "y": 0}) f.result() # Calculate offset approximation try: f = aligndelphi.LensAlignment(overview_ccd, sem_stage, logpath) position = f.result() except IOError as ex: if not force_calib: position = (offset[0] * scaling[0], offset[1] * scaling[1]) else: position = (0, 0) logging.warning( "Failed to locate the optical lens (%s), will used previous value %s", ex, position) # Just to check if move makes sense f = sem_stage.moveAbs({"x": position[0], "y": position[1]}) f.result() # Move to SEM f = chamber.moveAbs({"pressure": vacuum_pressure}) f.result() # Set basic e-beam settings escan.spotSize.value = 2.7 escan.accelVoltage.value = 5300 # V # Detect the holes/markers of the sample holder while True: ans = "Y" if force_calib else None while ans not in YES_NO_CHARS: msg = "\033[1;35mDo you want to execute the sample holder hole detection? [Y/n]\033[1;m" ans = raw_input(msg) if ans in YES_CHARS: # Move Phenom sample stage next to expected hole position sem_stage.moveAbsSync(aligndelphi.SHIFT_DETECTION) ebeam_focus.moveAbsSync({"z": hole_focus}) # Set the FoV to almost 2mm escan.horizontalFoV.value = escan.horizontalFoV.range[1] msg = "\033[1;34mPlease turn on the SEM stream and focus the SEM image. Then turn off the stream and press Enter ...\033[1;m" raw_input(msg) print "\033[1;30mTrying to detect the holes/markers, please wait...\033[1;m" try: hole_detectionf = aligndelphi.HoleDetection( detector, escan, sem_stage, ebeam_focus, manual=True, logpath=logpath) new_first_hole, new_second_hole, new_hole_focus = hole_detectionf.result( ) print '\033[1;36m' print "Values computed: 1st hole: " + str(new_first_hole) print " 2st hole: " + str(new_second_hole) print " hole focus: " + str(new_hole_focus) print '\033[1;m' ans = "Y" if force_calib else None while ans not in YES_NO_CHARS: msg = "\033[1;35mDo you want to update the calibration file with these values? [Y/n]\033[1;m" ans = raw_input(msg) if ans in YES_CHARS: first_hole, second_hole, hole_focus = new_first_hole, new_second_hole, new_hole_focus calibconf.set_sh_calib(shid, first_hole, second_hole, hole_focus, opt_focus, offset, scaling, rotation, iscale, irot, iscale_xy, ishear, resa, resb, hfwa, scaleshift) break except IOError: print "\033[1;31mSample holder hole detection failed.\033[1;m" else: break f = sem_stage.moveAbs({"x": position[0], "y": position[1]}) f.result() f = opt_stage.moveAbs({"x": 0, "y": 0}) f.result() if hole_focus is not None: good_focus = hole_focus - aligndelphi.GOOD_FOCUS_OFFSET else: good_focus = aligndelphi.SEM_KNOWN_FOCUS - aligndelphi.GOOD_FOCUS_OFFSET f = ebeam_focus.moveAbs({"z": good_focus}) f.result() # Set min fov # We want to be as close as possible to the center when we are zoomed in escan.horizontalFoV.value = escan.horizontalFoV.range[0] pure_offset = None # Start with the best optical focus known so far f = focus.moveAbs({"z": opt_focus}) f.result() while True: ans = "Y" if force_calib else None while ans not in YES_NO_CHARS: msg = "\033[1;35mDo you want to execute the twin stage calibration? [Y/n]\033[1;m" ans = raw_input(msg) if ans in YES_CHARS: # Configure CCD and e-beam to write CL spots ccd.binning.value = (1, 1) ccd.resolution.value = ccd.resolution.range[1] ccd.exposureTime.value = 900e-03 escan.scale.value = (1, 1) escan.resolution.value = (1, 1) escan.translation.value = (0, 0) if not escan.rotation.readonly: escan.rotation.value = 0 escan.shift.value = (0, 0) escan.dwellTime.value = 5e-06 detector.data.subscribe(_discard_data) print "\033[1;34mPlease turn on the Optical stream, set Power to 0 Watt and focus the image so you have a clearly visible spot.\033[1;m" print "\033[1;34mUse the up and down arrows or the mouse to move the optical focus and right and left arrows to move the SEM focus. Then turn off the stream and press Enter ...\033[1;m" if not force_calib: print "\033[1;33mIf you cannot see the whole source background (bright circle) you may try to move to the already known offset position. \nTo do this press the R key at any moment.\033[1;m" rollback_pos = (offset[0] * scaling[0], offset[1] * scaling[1]) else: rollback_pos = None ar = ArrowFocus(sem_stage, focus, ebeam_focus, ccd.depthOfField.value, escan.depthOfField.value) ar.focusByArrow(rollback_pos) detector.data.unsubscribe(_discard_data) print "\033[1;30mFine alignment in progress, please wait...\033[1;m" try: # TODO: the first point (at 0,0) isn't different from the next 4 points, # excepted it might be a little harder to focus. # => use the same code for all of them align_offsetf = aligndelphi.AlignAndOffset( ccd, detector, escan, sem_stage, opt_stage, focus, logpath) align_offset = align_offsetf.result() new_opt_focus = focus.position.value.get('z') def ask_user_to_focus(n): detector.data.subscribe(_discard_data) msg = ( "\033[1;34mAbout to calculate rotation and scaling (%d/4). " "Please turn on the Optical stream, " "set Power to 0 Watt and focus the image using the mouse " "so you have a clearly visible spot. \n" "If you do not see a spot nor the source background, " "move the sem-stage from the command line by steps of 200um " "in x and y until you can see the source background at the center. \n" "Then turn off the stream and press Enter ...\033[1;m" % (n + 1, )) raw_input(msg) # TODO: use ArrowFocus() too? print "\033[1;30mCalculating rotation and scaling (%d/4), please wait...\033[1;m" % ( n + 1, ) detector.data.unsubscribe(_discard_data) f = aligndelphi.RotationAndScaling( ccd, detector, escan, sem_stage, opt_stage, focus, align_offset, manual=ask_user_to_focus, logpath=logpath) acc_offset, new_rotation, new_scaling = f.result() # Offset is divided by scaling, since Convert Stage applies scaling # also in the given offset pure_offset = acc_offset new_offset = ((acc_offset[0] / new_scaling[0]), (acc_offset[1] / new_scaling[1])) print '\033[1;36m' print "Values computed: offset: " + str(new_offset) print " scaling: " + str(new_scaling) print " rotation: " + str(new_rotation) print " optical focus: " + str( new_opt_focus) print '\033[1;m' ans = "Y" if force_calib else None while ans not in YES_NO_CHARS: msg = "\033[1;35mDo you want to update the calibration file with these values? [Y/n]\033[1;m" ans = raw_input(msg) if ans in YES_CHARS: offset, scaling, rotation, opt_focus = new_offset, new_scaling, new_rotation, new_opt_focus calibconf.set_sh_calib(shid, first_hole, second_hole, hole_focus, opt_focus, offset, scaling, rotation, iscale, irot, iscale_xy, ishear, resa, resb, hfwa, scaleshift) break except IOError: print "\033[1;31mTwin stage calibration failed.\033[1;m" else: break while True: ans = "Y" if force_calib else None while ans not in YES_NO_CHARS: msg = "\033[1;35mDo you want to execute the fine alignment? [Y/n]\033[1;m" ans = raw_input(msg) if ans in YES_CHARS: # Return to the center so fine alignment can be executed just after calibration f = opt_stage.moveAbs({"x": 0, "y": 0}) f.result() if pure_offset is not None: f = sem_stage.moveAbs({ "x": pure_offset[0], "y": pure_offset[1] }) f.result() elif offset is not None: f = sem_stage.moveAbs({ "x": offset[0] * scaling[0], "y": offset[1] * scaling[1] }) f.result() else: f = sem_stage.moveAbs({"x": position[0], "y": position[1]}) f.result() f = focus.moveAbs({"z": opt_focus}) f.result() f = ebeam_focus.moveAbs({"z": good_focus}) f.result() # Run the optical fine alignment # TODO: reuse the exposure time # Configure e-beam to write CL spots escan.horizontalFoV.value = escan.horizontalFoV.range[0] escan.scale.value = (1, 1) escan.resolution.value = (1, 1) escan.translation.value = (0, 0) if not escan.rotation.readonly: escan.rotation.value = 0 escan.shift.value = (0, 0) escan.dwellTime.value = 5e-06 detector.data.subscribe(_discard_data) print "\033[1;34mPlease turn on the Optical stream, set Power to 0 Watt and focus the image so you have a clearly visible spot.\033[1;m" print "\033[1;34mUse the up and down arrows or the mouse to move the optical focus and right and left arrows to move the SEM focus. Then turn off the stream and press Enter ...\033[1;m" ar = ArrowFocus(sem_stage, focus, ebeam_focus, ccd.depthOfField.value, escan.depthOfField.value) ar.focusByArrow() detector.data.unsubscribe(_discard_data) # restore CCD settings (as the GUI/user might have changed them) ccd.binning.value = (1, 1) ccd.resolution.value = ccd.resolution.range[1] ccd.exposureTime.value = 900e-03 # Center (roughly) the spot on the CCD f = spot.CenterSpot(ccd, sem_stage, escan, spot.ROUGH_MOVE, spot.STAGE_MOVE, detector.data) dist, vect = f.result() if dist is None: logging.warning( "Failed to find a spot, twin stage calibration might have failed" ) print "\033[1;30mFine alignment in progress, please wait...\033[1;m" try: escan.horizontalFoV.value = 80e-06 f = align.FindOverlay( (4, 4), 0.5, # s, dwell time 10e-06, # m, maximum difference allowed escan, ccd, detector, skew=True, bgsub=True) trans_val, cor_md = f.result() trans_md, skew_md = cor_md new_iscale = trans_md[model.MD_PIXEL_SIZE_COR] new_irot = -trans_md[model.MD_ROTATION_COR] % (2 * math.pi) new_ishear = skew_md[model.MD_SHEAR_COR] new_iscale_xy = skew_md[model.MD_PIXEL_SIZE_COR] print '\033[1;36m' print "Values computed: scale: " + str(new_iscale) print " rotation: " + str(new_irot) print " scale-xy: " + str(new_iscale_xy) print " shear: " + str(new_ishear) print '\033[1;m' ans = "Y" if force_calib else None while ans not in YES_NO_CHARS: msg = "\033[1;35mDo you want to update the calibration file with these values? [Y/n]\033[1;m" ans = raw_input(msg) if ans in YES_CHARS: iscale, irot, iscale_xy, ishear = new_iscale, new_irot, new_iscale_xy, new_ishear calibconf.set_sh_calib(shid, first_hole, second_hole, hole_focus, opt_focus, offset, scaling, rotation, iscale, irot, iscale_xy, ishear, resa, resb, hfwa, scaleshift) break except ValueError: print "\033[1;31mFine alignment failed.\033[1;m" else: break while True: ans = "Y" if force_calib else None while ans not in YES_NO_CHARS: msg = "\033[1;35mDo you want to execute the SEM image calibration? [Y/n]\033[1;m" ans = raw_input(msg) if ans in YES_CHARS: # Resetting shift parameters, to not take them into account during calib blank_md = dict.fromkeys(aligndelphi.MD_CALIB_SEM, (0, 0)) escan.updateMetadata(blank_md) # We measure the shift in the area just behind the hole where there # are always some features plus the edge of the sample carrier. For # that reason we use the focus measured in the hole detection step f = sem_stage.moveAbs(aligndelphi.SHIFT_DETECTION) f.result() f = ebeam_focus.moveAbs({"z": hole_focus}) f.result() try: # Compute spot shift percentage print "\033[1;30mSpot shift measurement in progress, please wait...\033[1;m" f = aligndelphi.ScaleShiftFactor(detector, escan, logpath) new_scaleshift = f.result() # Compute resolution-related values. print "\033[1;30mCalculating resolution shift, please wait...\033[1;m" resolution_shiftf = aligndelphi.ResolutionShiftFactor( detector, escan, logpath) new_resa, new_resb = resolution_shiftf.result() # Compute HFW-related values print "\033[1;30mCalculating HFW shift, please wait...\033[1;m" hfw_shiftf = aligndelphi.HFWShiftFactor( detector, escan, logpath) new_hfwa = hfw_shiftf.result() print '\033[1;36m' print "Values computed: resolution-a: " + str(new_resa) print " resolution-b: " + str(new_resb) print " hfw-a: " + str(new_hfwa) print " spot shift: " + str(new_scaleshift) print '\033[1;m' ans = "Y" if force_calib else None while ans not in YES_NO_CHARS: msg = "\033[1;35mDo you want to update the calibration file with these values? [Y/n]\033[1;m" ans = raw_input(msg) if ans in YES_CHARS: resa, resb, hfwa, scaleshift = new_resa, new_resb, new_hfwa, new_scaleshift calibconf.set_sh_calib(shid, first_hole, second_hole, hole_focus, opt_focus, offset, scaling, rotation, iscale, irot, iscale_xy, ishear, resa, resb, hfwa, scaleshift) break except IOError: print "\033[1;31mSEM image calibration failed.\033[1;m" else: break # Update calibration file print "\033[1;30mUpdating calibration file is done\033[1;m" finally: print "\033[1;30mCalibration ended, now ejecting sample, please wait...\033[1;m" # Eject the sample holder f = chamber.moveAbs({"pressure": vented_pressure}) aligndelphi.restore_hw_settings(escan, ccd, hw_settings) # Store the final version of the calibration file try: shutil.copy(calibconf.file_path, logpath) except Exception: logging.info("Failed to log calibration file", exc_info=True) f.result()
def man_calib(logpath): escan = None detector = None ccd = None # find components by their role for c in model.getComponents(): if c.role == "e-beam": escan = c elif c.role == "bs-detector": detector = c elif c.role == "ccd": ccd = c elif c.role == "sem-stage": sem_stage = c elif c.role == "align": opt_stage = c elif c.role == "ebeam-focus": ebeam_focus = c elif c.role == "overview-focus": navcam_focus = c elif c.role == "focus": focus = c elif c.role == "overview-ccd": overview_ccd = c elif c.role == "chamber": chamber = c if not all([escan, detector, ccd]): logging.error("Failed to find all the components") raise KeyError("Not all components found") hw_settings = aligndelphi.list_hw_settings(escan, ccd) try: # Get pressure values pressures = chamber.axes["pressure"].choices vacuum_pressure = min(pressures.keys()) vented_pressure = max(pressures.keys()) if overview_ccd: for p, pn in pressures.items(): if pn == "overview": overview_pressure = p break else: raise IOError("Failed to find the overview pressure in %s" % (pressures,)) calibconf = get_calib_conf() shid, sht = chamber.sampleHolder.value calib_values = calibconf.get_sh_calib(shid) if calib_values is None: first_hole = second_hole = offset = resa = resb = hfwa = spotshift = (0, 0) scaling = iscale = iscale_xy = (1, 1) rotation = irot = ishear = 0 hole_focus = aligndelphi.SEM_KNOWN_FOCUS opt_focus = aligndelphi.OPTICAL_KNOWN_FOCUS print "\033[1;31mCalibration values missing! All the steps will be performed anyway...\033[1;m" force_calib = True else: first_hole, second_hole, hole_focus, opt_focus, offset, scaling, rotation, iscale, irot, iscale_xy, ishear, resa, resb, hfwa, spotshift = calib_values force_calib = False print "\033[1;36m" print "**Delphi Manual Calibration steps**" print "1.Sample holder hole detection" print " Current values: 1st hole: " + str(first_hole) print " 2st hole: " + str(second_hole) print " hole focus: " + str(hole_focus) print "2.Twin stage calibration" print " Current values: offset: " + str(offset) print " scaling: " + str(scaling) print " rotation: " + str(rotation) print " optical focus: " + str(opt_focus) print "3.Fine alignment" print " Current values: scale: " + str(iscale) print " rotation: " + str(irot) print " scale-xy: " + str(iscale_xy) print " shear: " + str(ishear) print "4.SEM image calibration" print " Current values: resolution-a: " + str(resa) print " resolution-b: " + str(resb) print " hfw-a: " + str(hfwa) print " spot shift: " + str(spotshift) print '\033[1;m' print "\033[1;33mNote that you should not perform any stage move during the process. \nInstead, you may zoom in/out while focusing.\033[1;m" print "\033[1;30mNow initializing, please wait...\033[1;m" # Move to the overview position first f = chamber.moveAbs({"pressure": overview_pressure}) f.result() # Reference the (optical) stage f = opt_stage.reference({"x", "y"}) f.result() f = focus.reference({"z"}) f.result() # SEM stage to (0,0) f = sem_stage.moveAbs({"x": 0, "y": 0}) f.result() # Calculate offset approximation try: f = aligndelphi.LensAlignment(overview_ccd, sem_stage, logpath) position = f.result() except IOError as ex: if not force_calib: position = (offset[0] * scaling[0], offset[1] * scaling[1]) else: position = (0, 0) logging.warning("Failed to locate the optical lens (%s), will used previous value %s", ex, position) # Just to check if move makes sense f = sem_stage.moveAbs({"x": position[0], "y": position[1]}) f.result() # Move to SEM f = chamber.moveAbs({"pressure": vacuum_pressure}) f.result() # Set basic e-beam settings escan.spotSize.value = 2.7 escan.accelVoltage.value = 5300 # V # Detect the holes/markers of the sample holder while True: ans = "Y" if force_calib else None while ans not in YES_NO_CHARS: msg = "\033[1;35mDo you want to execute the sample holder hole detection? [Y/n]\033[1;m" ans = raw_input(msg) if ans in YES_CHARS: # Move Phenom sample stage next to expected hole position sem_stage.moveAbsSync(aligndelphi.SHIFT_DETECTION) ebeam_focus.moveAbsSync({"z": hole_focus}) # Set the FoV to almost 2mm escan.horizontalFoV.value = escan.horizontalFoV.range[1] msg = "\033[1;34mPlease turn on the SEM stream and focus the SEM image. Then turn off the stream and press Enter ...\033[1;m" raw_input(msg) print "\033[1;30mTrying to detect the holes/markers, please wait...\033[1;m" try: hole_detectionf = aligndelphi.HoleDetection(detector, escan, sem_stage, ebeam_focus, manual=True, logpath=logpath) new_first_hole, new_second_hole, new_hole_focus = hole_detectionf.result() print '\033[1;36m' print "Values computed: 1st hole: " + str(new_first_hole) print " 2st hole: " + str(new_second_hole) print " hole focus: " + str(new_hole_focus) print '\033[1;m' ans = "Y" if force_calib else None while ans not in YES_NO_CHARS: msg = "\033[1;35mDo you want to update the calibration file with these values? [Y/n]\033[1;m" ans = raw_input(msg) if ans in YES_CHARS: first_hole, second_hole, hole_focus = new_first_hole, new_second_hole, new_hole_focus calibconf.set_sh_calib(shid, first_hole, second_hole, hole_focus, opt_focus, offset, scaling, rotation, iscale, irot, iscale_xy, ishear, resa, resb, hfwa, spotshift) break except IOError: print "\033[1;31mSample holder hole detection failed.\033[1;m" else: break f = sem_stage.moveAbs({"x": position[0], "y": position[1]}) f.result() f = opt_stage.moveAbs({"x": 0, "y": 0}) f.result() if hole_focus is not None: good_focus = hole_focus - aligndelphi.GOOD_FOCUS_OFFSET else: good_focus = aligndelphi.SEM_KNOWN_FOCUS - aligndelphi.GOOD_FOCUS_OFFSET f = ebeam_focus.moveAbs({"z": good_focus}) f.result() # Set min fov # We want to be as close as possible to the center when we are zoomed in escan.horizontalFoV.value = escan.horizontalFoV.range[0] pure_offset = None # Start with the best optical focus known so far f = focus.moveAbs({"z": opt_focus}) f.result() while True: ans = "Y" if force_calib else None while ans not in YES_NO_CHARS: msg = "\033[1;35mDo you want to execute the twin stage calibration? [Y/n]\033[1;m" ans = raw_input(msg) if ans in YES_CHARS: # Configure CCD and e-beam to write CL spots ccd.binning.value = (1, 1) ccd.resolution.value = ccd.resolution.range[1] ccd.exposureTime.value = 900e-03 escan.scale.value = (1, 1) escan.resolution.value = (1, 1) escan.translation.value = (0, 0) if not escan.rotation.readonly: escan.rotation.value = 0 escan.shift.value = (0, 0) escan.dwellTime.value = 5e-06 detector.data.subscribe(_discard_data) print "\033[1;34mPlease turn on the Optical stream, set Power to 0 Watt and focus the image so you have a clearly visible spot.\033[1;m" print "\033[1;34mUse the up and down arrows or the mouse to move the optical focus and right and left arrows to move the SEM focus. Then turn off the stream and press Enter ...\033[1;m" if not force_calib: print "\033[1;33mIf you cannot see the whole source background (bright circle) you may try to move to the already known offset position. \nTo do this press the R key at any moment.\033[1;m" rollback_pos = (offset[0] * scaling[0], offset[1] * scaling[1]) else: rollback_pos = None ar = ArrowFocus(sem_stage, focus, ebeam_focus, ccd.depthOfField.value, escan.depthOfField.value) ar.focusByArrow(rollback_pos) detector.data.unsubscribe(_discard_data) print "\033[1;30mFine alignment in progress, please wait...\033[1;m" try: # TODO: the first point (at 0,0) isn't different from the next 4 points, # excepted it might be a little harder to focus. # => use the same code for all of them align_offsetf = aligndelphi.AlignAndOffset(ccd, detector, escan, sem_stage, opt_stage, focus, logpath) align_offset = align_offsetf.result() new_opt_focus = focus.position.value.get('z') def ask_user_to_focus(n): detector.data.subscribe(_discard_data) msg = ("\033[1;34mAbout to calculate rotation and scaling (%d/4). " "Please turn on the Optical stream, " "set Power to 0 Watt and focus the image using the mouse " "so you have a clearly visible spot. \n" "If you do not see a spot nor the source background, " "move the sem-stage from the command line by steps of 200um " "in x and y until you can see the source background at the center. \n" "Then turn off the stream and press Enter ...\033[1;m" % (n + 1,)) raw_input(msg) # TODO: use ArrowFocus() too? print "\033[1;30mCalculating rotation and scaling (%d/4), please wait...\033[1;m" % (n + 1,) detector.data.unsubscribe(_discard_data) f = aligndelphi.RotationAndScaling(ccd, detector, escan, sem_stage, opt_stage, focus, align_offset, manual=ask_user_to_focus, logpath=logpath) acc_offset, new_rotation, new_scaling = f.result() # Offset is divided by scaling, since Convert Stage applies scaling # also in the given offset pure_offset = acc_offset new_offset = ((acc_offset[0] / new_scaling[0]), (acc_offset[1] / new_scaling[1])) print '\033[1;36m' print "Values computed: offset: " + str(new_offset) print " scaling: " + str(new_scaling) print " rotation: " + str(new_rotation) print " optical focus: " + str(new_opt_focus) print '\033[1;m' ans = "Y" if force_calib else None while ans not in YES_NO_CHARS: msg = "\033[1;35mDo you want to update the calibration file with these values? [Y/n]\033[1;m" ans = raw_input(msg) if ans in YES_CHARS: offset, scaling, rotation, opt_focus = new_offset, new_scaling, new_rotation, new_opt_focus calibconf.set_sh_calib(shid, first_hole, second_hole, hole_focus, opt_focus, offset, scaling, rotation, iscale, irot, iscale_xy, ishear, resa, resb, hfwa, spotshift) break except IOError: print "\033[1;31mTwin stage calibration failed.\033[1;m" else: break while True: ans = "Y" if force_calib else None while ans not in YES_NO_CHARS: msg = "\033[1;35mDo you want to execute the fine alignment? [Y/n]\033[1;m" ans = raw_input(msg) if ans in YES_CHARS: # Return to the center so fine alignment can be executed just after calibration f = opt_stage.moveAbs({"x": 0, "y": 0}) f.result() if pure_offset is not None: f = sem_stage.moveAbs({"x":pure_offset[0], "y":pure_offset[1]}) f.result() elif offset is not None: f = sem_stage.moveAbs({"x":offset[0] * scaling[0], "y":offset[1] * scaling[1]}) f.result() else: f = sem_stage.moveAbs({"x":position[0], "y":position[1]}) f.result() f = focus.moveAbs({"z": opt_focus}) f.result() f = ebeam_focus.moveAbs({"z": good_focus}) f.result() # Run the optical fine alignment # TODO: reuse the exposure time # Configure e-beam to write CL spots escan.horizontalFoV.value = escan.horizontalFoV.range[0] escan.scale.value = (1, 1) escan.resolution.value = (1, 1) escan.translation.value = (0, 0) if not escan.rotation.readonly: escan.rotation.value = 0 escan.shift.value = (0, 0) escan.dwellTime.value = 5e-06 detector.data.subscribe(_discard_data) print "\033[1;34mPlease turn on the Optical stream, set Power to 0 Watt and focus the image so you have a clearly visible spot.\033[1;m" print "\033[1;34mUse the up and down arrows or the mouse to move the optical focus and right and left arrows to move the SEM focus. Then turn off the stream and press Enter ...\033[1;m" ar = ArrowFocus(sem_stage, focus, ebeam_focus, ccd.depthOfField.value, escan.depthOfField.value) ar.focusByArrow() detector.data.unsubscribe(_discard_data) # restore CCD settings (as the GUI/user might have changed them) ccd.binning.value = (1, 1) ccd.resolution.value = ccd.resolution.range[1] ccd.exposureTime.value = 900e-03 # Center (roughly) the spot on the CCD f = spot.CenterSpot(ccd, sem_stage, escan, spot.ROUGH_MOVE, spot.STAGE_MOVE, detector.data) dist, vect = f.result() if dist is None: logging.warning("Failed to find a spot, twin stage calibration might have failed") print "\033[1;30mFine alignment in progress, please wait...\033[1;m" try: escan.horizontalFoV.value = 80e-06 f = align.FindOverlay((4, 4), 0.5, # s, dwell time 10e-06, # m, maximum difference allowed escan, ccd, detector, skew=True, bgsub=True) trans_val, cor_md = f.result() trans_md, skew_md = cor_md new_iscale = trans_md[model.MD_PIXEL_SIZE_COR] new_irot = -trans_md[model.MD_ROTATION_COR] % (2 * math.pi) new_ishear = skew_md[model.MD_SHEAR_COR] new_iscale_xy = skew_md[model.MD_PIXEL_SIZE_COR] print '\033[1;36m' print "Values computed: scale: " + str(new_iscale) print " rotation: " + str(new_irot) print " scale-xy: " + str(new_iscale_xy) print " shear: " + str(new_ishear) print '\033[1;m' ans = "Y" if force_calib else None while ans not in YES_NO_CHARS: msg = "\033[1;35mDo you want to update the calibration file with these values? [Y/n]\033[1;m" ans = raw_input(msg) if ans in YES_CHARS: iscale, irot, iscale_xy, ishear = new_iscale, new_irot, new_iscale_xy, new_ishear calibconf.set_sh_calib(shid, first_hole, second_hole, hole_focus, opt_focus, offset, scaling, rotation, iscale, irot, iscale_xy, ishear, resa, resb, hfwa, spotshift) break except ValueError: print "\033[1;31mFine alignment failed.\033[1;m" else: break while True: ans = "Y" if force_calib else None while ans not in YES_NO_CHARS: msg = "\033[1;35mDo you want to execute the SEM image calibration? [Y/n]\033[1;m" ans = raw_input(msg) if ans in YES_CHARS: # Resetting shift parameters, to not take them into account during calib blank_md = dict.fromkeys(aligndelphi.MD_CALIB_SEM, (0, 0)) escan.updateMetadata(blank_md) # We measure the shift in the area just behind the hole where there # are always some features plus the edge of the sample carrier. For # that reason we use the focus measured in the hole detection step f = sem_stage.moveAbs(aligndelphi.SHIFT_DETECTION) f.result() f = ebeam_focus.moveAbs({"z": hole_focus}) f.result() try: # Compute spot shift percentage print "\033[1;30mSpot shift measurement in progress, please wait...\033[1;m" f = aligndelphi.ScaleShiftFactor(detector, escan, logpath) new_spotshift = f.result() # Compute resolution-related values. print "\033[1;30mCalculating resolution shift, please wait...\033[1;m" resolution_shiftf = aligndelphi.ResolutionShiftFactor(detector, escan, logpath) new_resa, new_resb = resolution_shiftf.result() # Compute HFW-related values print "\033[1;30mCalculating HFW shift, please wait...\033[1;m" hfw_shiftf = aligndelphi.HFWShiftFactor(detector, escan, logpath) new_hfwa = hfw_shiftf.result() print '\033[1;36m' print "Values computed: resolution-a: " + str(new_resa) print " resolution-b: " + str(new_resb) print " hfw-a: " + str(new_hfwa) print " spot shift: " + str(new_spotshift) print '\033[1;m' ans = "Y" if force_calib else None while ans not in YES_NO_CHARS: msg = "\033[1;35mDo you want to update the calibration file with these values? [Y/n]\033[1;m" ans = raw_input(msg) if ans in YES_CHARS: resa, resb, hfwa, spotshift = new_resa, new_resb, new_hfwa, new_spotshift calibconf.set_sh_calib(shid, first_hole, second_hole, hole_focus, opt_focus, offset, scaling, rotation, iscale, irot, iscale_xy, ishear, resa, resb, hfwa, spotshift) break except IOError: print "\033[1;31mSEM image calibration failed.\033[1;m" else: break # Update calibration file print "\033[1;30mUpdating calibration file is done\033[1;m" finally: print "\033[1;30mCalibration ended, now ejecting sample, please wait...\033[1;m" # Eject the sample holder f = chamber.moveAbs({"pressure": vented_pressure}) aligndelphi.restore_hw_settings(escan, ccd, hw_settings) # Store the final version of the calibration file try: shutil.copy(calibconf.file_path, logpath) except Exception: logging.info("Failed to log calibration file", exc_info=True) f.result()