diff --git a/fibsem/microscope.py b/fibsem/microscope.py index e0c9f8de..e39f85b5 100644 --- a/fibsem/microscope.py +++ b/fibsem/microscope.py @@ -1625,6 +1625,24 @@ def run_milling_drift_corrected(self, milling_current: float, logging.info(f"Patterning State: {self.connection.patterning.state}") + + # if ref_image -> do drift correction + # get reduced_area from ref_image + # user defined interval? + # run milling (always async?) + # pause milling + # drift correction + # resume milling + # clear patterns? + # make the milling states universal, then we can write one function? + + # mill settings: + # reduced_area: FibsemRectangle = None, + # drift_correction: bool = False, + # drift_interval: float = 60.0, + + + def finish_milling(self, imaging_current: float, imaging_voltage: float): """ Finalises the milling process by clearing the microscope of any patterns and returning the current to the imaging current. @@ -5513,6 +5531,8 @@ def draw_bitmap_pattern(self, pattern_settings: FibsemBitmapSettings, return def run_milling_drift_corrected(self): _check_beam(BeamType.ION, self.system) + time.sleep(5) + logging.debug({"msg": "run_milling", "milling_current": milling_current, "milling_voltage": milling_voltage, "asynch": asynch}) return def setup_sputter(self, protocol: dict) -> None: diff --git a/fibsem/structures.py b/fibsem/structures.py index 2cb68cb3..2798d4ef 100644 --- a/fibsem/structures.py +++ b/fibsem/structures.py @@ -1024,6 +1024,9 @@ class FibsemMillingSettings: preset: str = "30 keV; UHR imaging" spacing: float = 1.0 milling_voltage: float = 30e3 + drift_correction: bool = False + drift_correction_area: FibsemRectangle = None + drift_correction_interval: int = 30 def __post_init__(self): assert isinstance( @@ -1064,6 +1067,11 @@ def to_dict(self) -> dict: "preset": self.preset, "spacing": self.spacing, "milling_voltage": self.milling_voltage, + "drift_correction": self.drift_correction, + "drift_correction_area": self.drift_correction_area.to_dict() + if self.drift_correction_area is not None + else None, + "drift_correction_interval": self.drift_correction_interval, } return settings_dict @@ -1081,6 +1089,11 @@ def from_dict(settings: dict) -> "FibsemMillingSettings": preset=settings.get("preset", "30 keV; 1nA"), spacing=settings.get("spacing", 1.0), milling_voltage=settings.get("milling_voltage", 30e3), + drift_correction=settings.get("drift_correction", False), + drift_correction_area=FibsemRectangle.from_dict( + settings.get("drift_correction_area", None) + ), + drift_correction_interval=settings.get("drift_correction_interval", 30), ) return milling_settings diff --git a/fibsem/ui/FibsemMillingWidget.py b/fibsem/ui/FibsemMillingWidget.py index 31953409..c593b2f7 100644 --- a/fibsem/ui/FibsemMillingWidget.py +++ b/fibsem/ui/FibsemMillingWidget.py @@ -15,7 +15,7 @@ from fibsem.patterning import FibsemMillingStage from fibsem.structures import (BeamType, FibsemMillingSettings, MicroscopeSettings, - Point) + Point, FibsemRectangle) from fibsem.ui.FibsemImageSettingsWidget import FibsemImageSettingsWidget from fibsem.ui.qtdesigner_files import FibsemMillingWidget from fibsem.ui.utils import (_draw_patterns_in_napari, _remove_all_layers, @@ -79,12 +79,16 @@ def __init__( self.milling_stages = milling_stages + self.reduced_area = None # tmp holder for reduced area + self.setup_connections() self.update_pattern_ui() self.good_copy_pattern = None + self.alignment_layer = None + self.updating_alignment_area: bool = False self._UPDATING_PATTERN:bool = False self._PATTERN_IS_MOVEABLE: bool = True @@ -203,6 +207,150 @@ def setup_connections(self): self.label_milling_instructions.setText(_MILLING_WIDGET_INSTRUCTIONS) + + self.pushButton_edit_drift_correction_area.clicked.connect(self.edit_drift_correction_area) + + + self.spinBox_milling_drift_correction_interval.valueChanged.connect(self.update_settings) + self.checkBox_milling_drift_correction.stateChanged.connect(self.update_settings) + self.checkBox_milling_drift_correction.stateChanged.connect(self.toggle_drift_correction) + self.toggle_drift_correction() + + def toggle_drift_correction(self): + + enabled = self.checkBox_milling_drift_correction.isChecked() + + self.label_milling_drift_correction_interval.setVisible(enabled) + self.spinBox_milling_drift_correction_interval.setVisible(enabled) + self.pushButton_edit_drift_correction_area.setVisible(enabled) + + + def edit_drift_correction_area(self): + print("edit alignment") + + # toggle the alignment widget + if self.updating_alignment_area: + self.updating_alignment_area = False + self.pushButton_edit_drift_correction_area.setStyleSheet(_stylesheets._GREEN_PUSHBUTTON_STYLE) + self.pushButton_edit_drift_correction_area.setText("Edit Alignment Area") + else: + self.updating_alignment_area = True + self.pushButton_edit_drift_correction_area.setStyleSheet(_stylesheets._ORANGE_PUSHBUTTON_STYLE) + self.pushButton_edit_drift_correction_area.setText("Editing Alignment Area") + + if self.updating_alignment_area: + + if self.alignment_layer is None: + + def create_default_alignment_area(ion_shape, electron_shape, w: int = 150, h: int = 150) -> np.ndarray: + """Create the default alignment area, based on the shape of the ion and electron images.""" + icx = electron_shape[1] + ion_shape[1] // 2 + icy = ion_shape[0] // 2 + x0, y0 = icx - w, icy - h + x1, y1 = icx + w, icy + h + data = [[y0, x0], [y0, x1], [y1, x1], [y1, x0]] + return data + + def convert_reduced_area_to_napari_shape(reduced_area: FibsemRectangle, image_shape: tuple, offset_shape: tuple = None) -> np.ndarray: + """Convert a reduced area to a napari shape.""" + x0 = reduced_area.left * image_shape[1] + y0 = reduced_area.top * image_shape[0] + if offset_shape: + x0 += offset_shape[1] + x1 = x0 + reduced_area.width * image_shape[1] + y1 = y0 + reduced_area.height * image_shape[0] + data = [[y0, x0], [y0, x1], [y1, x1], [y1, x0]] + return data + + + data = convert_reduced_area_to_napari_shape(FibsemRectangle(0.375, 0.375, 0.25, 0.25), self.image_widget.ib_image.data.shape, self.image_widget.eb_image.data.shape) + # TODO: create as FibsemRectangle -> Convert to shape + # data = create_default_alignment_area(self.image_widget.ib_image.data.shape, self.image_widget.eb_image.data.shape) + + self.alignment_layer = self.viewer.add_shapes(data=data, name="alignment_area", + shape_type="rectangle", edge_color="red", + face_color="red", opacity=0.5) + self.alignment_layer.metadata = {"type": "alignment"} + self.alignment_layer.events.data.connect(self.update_alignment) + + + self.viewer.layers.selection.active = self.alignment_layer + self.alignment_layer.mode = "select" + + + else: + self.viewer.layers.selection.active = self.image_widget.eb_layer + + # TODO: save alignment rectangle to milling stage + # TODO: error check to make sure the alignment rectangle is within the image + # TODO: convert to FibsemRectangle format + + + def update_alignment(self): + + if self.alignment_layer is not None: + data = self.alignment_layer.data + + # convert to FibsemRectangle format where rectangle is defined by the top left corner, + # and the width and height expressed as percentage of the image + + rect = data[0] + + def convert_shape_to_image_area(shape: list[list[int]], image_shape: tuple, offset_shape: tuple = None) -> FibsemRectangle: + """Convert a napari shape (rectangle) to a FibsemRectangle expressed as a percentage of the image (reduced area) + shape: the coordinates of the shape + image_shape: the shape of the image (usually the ion beam image) + offset_shape: the shape of the offset image (usually the electron beam image, as it translates to the ion beam image) + + """ + # get limits of rectangle + y0, x0 = rect[0] + y1, x1 = rect[2] + + # subtract shape of eb image + if offset_shape: + x0 -= offset_shape[1] + x1 -= offset_shape[1] + + # convert to percentage of image + x0 = x0 / image_shape[1] + x1 = x1 / image_shape[1] + y0 = y0 / image_shape[0] + y1 = y1 / image_shape[0] + w = x1 - x0 + h = y1 - y0 + + reduced_area = FibsemRectangle(left=x0, top=y0, width=w, height=h) + print("Reduced Area: ", reduced_area) + + return reduced_area + + def is_valid_reduced_area(reduced_area: FibsemRectangle) -> bool: + """Check whether the reduced area is valid. + Left and top must be between 0 and 1, and width and height must be between 0 and 1. + Must not exceed the boundaries of the image 0 - 1 + """ + # if left or top is less than 0, or width or height is greater than 1, return False + if reduced_area.left < 0 or reduced_area.top < 0 or reduced_area.width > 1 or reduced_area.height > 1: + return False + if reduced_area.left + reduced_area.width > 1 or reduced_area.top + reduced_area.height > 1: + return False + # no negative values + if reduced_area.left < 0 or reduced_area.top < 0 or reduced_area.width < 0 or reduced_area.height < 0: + return False + return True + + reduced_area = convert_shape_to_image_area(rect, self.image_widget.eb_image.data.shape, self.image_widget.ib_image.data.shape) + + print("Reduced Area: ", reduced_area) + is_valid = is_valid_reduced_area(reduced_area) + print(f"Valid Reduced Area: {is_valid}") + + if not is_valid: + napari.utils.notifications.show_warning("Invalid Alignment Area, please adjust the alignment area to be within the image.") + return + self.reduced_area = reduced_area + self.update_settings() def update_settings(self): settings = self.get_milling_settings_from_ui() @@ -228,6 +376,8 @@ def remove_milling_stage(self): logging.info("Removing milling stage") current_index = self.comboBox_milling_stage.currentIndex() + if current_index == -1: + return log_status_message(self.milling_stages[current_index], "REMOVED_STAGE") self.milling_stages.pop(current_index) self.comboBox_milling_stage.removeItem(current_index) @@ -486,7 +636,7 @@ def _single_click(self, layer, event): if beam_type is not BeamType.ION: napari.utils.notifications.show_info( - f"Please right click on the {BeamType.ION.name} image to move pattern." + f"Please click on the {BeamType.ION.name} image to move pattern." ) return @@ -609,6 +759,9 @@ def set_milling_settings_ui(self, milling: FibsemMillingSettings) -> None: self.doubleSpinBox_hfw.setValue(milling.hfw * constants.SI_TO_MICRO) self.comboBox_preset.setCurrentText(str(milling.preset)) self.spinBox_voltage.setValue(milling.milling_voltage) + self.doubleSpinBox_spacing.setValue(milling.spacing) + self.checkBox_milling_drift_correction.setChecked(milling.drift_correction) + self.spinBox_milling_drift_correction_interval.setValue(milling.drift_correction_interval) def get_milling_settings_from_ui(self): @@ -624,6 +777,9 @@ def get_milling_settings_from_ui(self): preset= self.comboBox_preset.currentText(), spacing=self.doubleSpinBox_spacing.value(), milling_voltage=self.spinBox_voltage.value(), + drift_correction=self.checkBox_milling_drift_correction.isChecked(), + drift_correction_interval=self.spinBox_milling_drift_correction_interval.value(), + drift_correction_area=self.reduced_area, ) return milling_settings diff --git a/fibsem/ui/qtdesigner_files/FibsemMillingWidget.py b/fibsem/ui/qtdesigner_files/FibsemMillingWidget.py index 273d0275..635fb72b 100644 --- a/fibsem/ui/qtdesigner_files/FibsemMillingWidget.py +++ b/fibsem/ui/qtdesigner_files/FibsemMillingWidget.py @@ -25,28 +25,29 @@ def setupUi(self, Form): self.frame.setObjectName("frame") self.gridLayout_4 = QtWidgets.QGridLayout(self.frame) self.gridLayout_4.setObjectName("gridLayout_4") - self.comboBox_milling_stage = QtWidgets.QComboBox(self.frame) - self.comboBox_milling_stage.setObjectName("comboBox_milling_stage") - self.gridLayout_4.addWidget(self.comboBox_milling_stage, 2, 1, 1, 1) - self.doubleSpinBox_centre_y = QtWidgets.QDoubleSpinBox(self.frame) - self.doubleSpinBox_centre_y.setMinimum(-1e+16) - self.doubleSpinBox_centre_y.setMaximum(1e+23) - self.doubleSpinBox_centre_y.setSingleStep(0.1) - self.doubleSpinBox_centre_y.setObjectName("doubleSpinBox_centre_y") - self.gridLayout_4.addWidget(self.doubleSpinBox_centre_y, 22, 1, 1, 1) + self.label_milling_header = QtWidgets.QLabel(self.frame) + font = QtGui.QFont() + font.setPointSize(10) + font.setBold(True) + font.setWeight(75) + self.label_milling_header.setFont(font) + self.label_milling_header.setObjectName("label_milling_header") + self.gridLayout_4.addWidget(self.label_milling_header, 5, 0, 1, 2) self.pushButton_remove_milling_stage = QtWidgets.QPushButton(self.frame) self.pushButton_remove_milling_stage.setObjectName("pushButton_remove_milling_stage") self.gridLayout_4.addWidget(self.pushButton_remove_milling_stage, 3, 1, 1, 1) - self.label_centre_x = QtWidgets.QLabel(self.frame) - self.label_centre_x.setObjectName("label_centre_x") - self.gridLayout_4.addWidget(self.label_centre_x, 21, 0, 1, 1) - self.label_hfw = QtWidgets.QLabel(self.frame) - self.label_hfw.setObjectName("label_hfw") - self.gridLayout_4.addWidget(self.label_hfw, 8, 0, 1, 1) - self.checkBox_relative_move = QtWidgets.QCheckBox(self.frame) - self.checkBox_relative_move.setChecked(True) - self.checkBox_relative_move.setObjectName("checkBox_relative_move") - self.gridLayout_4.addWidget(self.checkBox_relative_move, 27, 1, 1, 1) + self.label_voltage = QtWidgets.QLabel(self.frame) + self.label_voltage.setObjectName("label_voltage") + self.gridLayout_4.addWidget(self.label_voltage, 6, 0, 1, 1) + self.label_milling_current = QtWidgets.QLabel(self.frame) + self.label_milling_current.setObjectName("label_milling_current") + self.gridLayout_4.addWidget(self.label_milling_current, 7, 0, 1, 1) + self.doubleSpinBox_centre_x = QtWidgets.QDoubleSpinBox(self.frame) + self.doubleSpinBox_centre_x.setMinimum(-1e+28) + self.doubleSpinBox_centre_x.setMaximum(1e+18) + self.doubleSpinBox_centre_x.setSingleStep(0.1) + self.doubleSpinBox_centre_x.setObjectName("doubleSpinBox_centre_x") + self.gridLayout_4.addWidget(self.doubleSpinBox_centre_x, 23, 1, 1, 1) self.doubleSpinBox_dwell_time = QtWidgets.QDoubleSpinBox(self.frame) self.doubleSpinBox_dwell_time.setDecimals(4) self.doubleSpinBox_dwell_time.setMinimum(0.0) @@ -55,9 +56,56 @@ def setupUi(self, Form): self.doubleSpinBox_dwell_time.setProperty("value", 0.0) self.doubleSpinBox_dwell_time.setObjectName("doubleSpinBox_dwell_time") self.gridLayout_4.addWidget(self.doubleSpinBox_dwell_time, 11, 1, 1, 1) - self.checkBox_live_update = QtWidgets.QCheckBox(self.frame) - self.checkBox_live_update.setObjectName("checkBox_live_update") - self.gridLayout_4.addWidget(self.checkBox_live_update, 27, 0, 1, 1) + self.checkBox_relative_move = QtWidgets.QCheckBox(self.frame) + self.checkBox_relative_move.setChecked(True) + self.checkBox_relative_move.setObjectName("checkBox_relative_move") + self.gridLayout_4.addWidget(self.checkBox_relative_move, 29, 1, 1, 1) + self.label_pattern_set = QtWidgets.QLabel(self.frame) + self.label_pattern_set.setObjectName("label_pattern_set") + self.gridLayout_4.addWidget(self.label_pattern_set, 21, 0, 1, 1) + self.doubleSpinBox_milling_current = QtWidgets.QDoubleSpinBox(self.frame) + self.doubleSpinBox_milling_current.setObjectName("doubleSpinBox_milling_current") + self.gridLayout_4.addWidget(self.doubleSpinBox_milling_current, 7, 1, 1, 1) + self.comboBox_application_file = QtWidgets.QComboBox(self.frame) + self.comboBox_application_file.setObjectName("comboBox_application_file") + self.gridLayout_4.addWidget(self.comboBox_application_file, 9, 1, 1, 1) + self.label_application_file = QtWidgets.QLabel(self.frame) + self.label_application_file.setObjectName("label_application_file") + self.gridLayout_4.addWidget(self.label_application_file, 9, 0, 1, 1) + self.label_milling_instructions = QtWidgets.QLabel(self.frame) + self.label_milling_instructions.setObjectName("label_milling_instructions") + self.gridLayout_4.addWidget(self.label_milling_instructions, 31, 0, 1, 2) + self.label_milling_stage = QtWidgets.QLabel(self.frame) + self.label_milling_stage.setObjectName("label_milling_stage") + self.gridLayout_4.addWidget(self.label_milling_stage, 2, 0, 1, 1) + self.doubleSpinBox_hfw = QtWidgets.QDoubleSpinBox(self.frame) + self.doubleSpinBox_hfw.setEnabled(True) + self.doubleSpinBox_hfw.setReadOnly(True) + self.doubleSpinBox_hfw.setMinimum(10.0) + self.doubleSpinBox_hfw.setMaximum(1000000000.0) + self.doubleSpinBox_hfw.setProperty("value", 150.0) + self.doubleSpinBox_hfw.setObjectName("doubleSpinBox_hfw") + self.gridLayout_4.addWidget(self.doubleSpinBox_hfw, 8, 1, 1, 1) + self.label_patterns_header = QtWidgets.QLabel(self.frame) + font = QtGui.QFont() + font.setPointSize(10) + font.setBold(True) + font.setWeight(75) + self.label_patterns_header.setFont(font) + self.label_patterns_header.setObjectName("label_patterns_header") + self.gridLayout_4.addWidget(self.label_patterns_header, 19, 0, 1, 2) + self.pushButton = QtWidgets.QPushButton(self.frame) + self.pushButton.setObjectName("pushButton") + self.gridLayout_4.addWidget(self.pushButton, 32, 0, 1, 2) + self.comboBox_patterns = QtWidgets.QComboBox(self.frame) + self.comboBox_patterns.setObjectName("comboBox_patterns") + self.gridLayout_4.addWidget(self.comboBox_patterns, 21, 1, 1, 1) + self.pushButton_edit_drift_correction_area = QtWidgets.QPushButton(self.frame) + self.pushButton_edit_drift_correction_area.setObjectName("pushButton_edit_drift_correction_area") + self.gridLayout_4.addWidget(self.pushButton_edit_drift_correction_area, 15, 1, 1, 1) + self.gridLayout_patterns = QtWidgets.QGridLayout() + self.gridLayout_patterns.setObjectName("gridLayout_patterns") + self.gridLayout_4.addLayout(self.gridLayout_patterns, 26, 0, 2, 2) self.doubleSpinBox_spot_size = QtWidgets.QDoubleSpinBox(self.frame) self.doubleSpinBox_spot_size.setDecimals(4) self.doubleSpinBox_spot_size.setMinimum(0.0) @@ -66,6 +114,24 @@ def setupUi(self, Form): self.doubleSpinBox_spot_size.setProperty("value", 0.0) self.doubleSpinBox_spot_size.setObjectName("doubleSpinBox_spot_size") self.gridLayout_4.addWidget(self.doubleSpinBox_spot_size, 12, 1, 1, 1) + self.checkBox_milling_drift_correction = QtWidgets.QCheckBox(self.frame) + self.checkBox_milling_drift_correction.setObjectName("checkBox_milling_drift_correction") + self.gridLayout_4.addWidget(self.checkBox_milling_drift_correction, 15, 0, 1, 1) + self.doubleSpinBox_centre_y = QtWidgets.QDoubleSpinBox(self.frame) + self.doubleSpinBox_centre_y.setMinimum(-1e+16) + self.doubleSpinBox_centre_y.setMaximum(1e+23) + self.doubleSpinBox_centre_y.setSingleStep(0.1) + self.doubleSpinBox_centre_y.setObjectName("doubleSpinBox_centre_y") + self.gridLayout_4.addWidget(self.doubleSpinBox_centre_y, 24, 1, 1, 1) + self.label_dwell_time = QtWidgets.QLabel(self.frame) + self.label_dwell_time.setObjectName("label_dwell_time") + self.gridLayout_4.addWidget(self.label_dwell_time, 11, 0, 1, 1) + self.comboBox_milling_stage = QtWidgets.QComboBox(self.frame) + self.comboBox_milling_stage.setObjectName("comboBox_milling_stage") + self.gridLayout_4.addWidget(self.comboBox_milling_stage, 2, 1, 1, 1) + self.label_hfw = QtWidgets.QLabel(self.frame) + self.label_hfw.setObjectName("label_hfw") + self.gridLayout_4.addWidget(self.label_hfw, 8, 0, 1, 1) self.doubleSpinBox_rate = QtWidgets.QDoubleSpinBox(self.frame) self.doubleSpinBox_rate.setDecimals(4) self.doubleSpinBox_rate.setMinimum(0.0) @@ -74,118 +140,64 @@ def setupUi(self, Form): self.doubleSpinBox_rate.setProperty("value", 0.0) self.doubleSpinBox_rate.setObjectName("doubleSpinBox_rate") self.gridLayout_4.addWidget(self.doubleSpinBox_rate, 10, 1, 1, 1) - self.comboBox_patterns = QtWidgets.QComboBox(self.frame) - self.comboBox_patterns.setObjectName("comboBox_patterns") - self.gridLayout_4.addWidget(self.comboBox_patterns, 19, 1, 1, 1) self.label_centre_y = QtWidgets.QLabel(self.frame) self.label_centre_y.setObjectName("label_centre_y") - self.gridLayout_4.addWidget(self.label_centre_y, 22, 0, 1, 1) - self.label_voltage = QtWidgets.QLabel(self.frame) - self.label_voltage.setObjectName("label_voltage") - self.gridLayout_4.addWidget(self.label_voltage, 6, 0, 1, 1) - self.doubleSpinBox_spacing = QtWidgets.QDoubleSpinBox(self.frame) - self.doubleSpinBox_spacing.setProperty("value", 1.0) - self.doubleSpinBox_spacing.setObjectName("doubleSpinBox_spacing") - self.gridLayout_4.addWidget(self.doubleSpinBox_spacing, 13, 1, 1, 1) - self.gridLayout_patterns = QtWidgets.QGridLayout() - self.gridLayout_patterns.setObjectName("gridLayout_patterns") - self.gridLayout_4.addLayout(self.gridLayout_patterns, 24, 0, 2, 2) - self.line_3 = QtWidgets.QFrame(self.frame) - self.line_3.setFrameShape(QtWidgets.QFrame.HLine) - self.line_3.setFrameShadow(QtWidgets.QFrame.Sunken) - self.line_3.setObjectName("line_3") - self.gridLayout_4.addWidget(self.line_3, 4, 0, 1, 2) - spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) - self.gridLayout_4.addItem(spacerItem, 26, 0, 1, 2) - self.label_pattern_set = QtWidgets.QLabel(self.frame) - self.label_pattern_set.setObjectName("label_pattern_set") - self.gridLayout_4.addWidget(self.label_pattern_set, 19, 0, 1, 1) - self.comboBox_application_file = QtWidgets.QComboBox(self.frame) - self.comboBox_application_file.setObjectName("comboBox_application_file") - self.gridLayout_4.addWidget(self.comboBox_application_file, 9, 1, 1, 1) - self.label_milling_header = QtWidgets.QLabel(self.frame) - font = QtGui.QFont() - font.setPointSize(10) - font.setBold(True) - font.setWeight(75) - self.label_milling_header.setFont(font) - self.label_milling_header.setObjectName("label_milling_header") - self.gridLayout_4.addWidget(self.label_milling_header, 5, 0, 1, 2) - self.label_milling_instructions = QtWidgets.QLabel(self.frame) - self.label_milling_instructions.setObjectName("label_milling_instructions") - self.gridLayout_4.addWidget(self.label_milling_instructions, 29, 0, 1, 2) - self.doubleSpinBox_centre_x = QtWidgets.QDoubleSpinBox(self.frame) - self.doubleSpinBox_centre_x.setMinimum(-1e+28) - self.doubleSpinBox_centre_x.setMaximum(1e+18) - self.doubleSpinBox_centre_x.setSingleStep(0.1) - self.doubleSpinBox_centre_x.setObjectName("doubleSpinBox_centre_x") - self.gridLayout_4.addWidget(self.doubleSpinBox_centre_x, 21, 1, 1, 1) - self.label_spot_size = QtWidgets.QLabel(self.frame) - self.label_spot_size.setObjectName("label_spot_size") - self.gridLayout_4.addWidget(self.label_spot_size, 12, 0, 1, 1) + self.gridLayout_4.addWidget(self.label_centre_y, 24, 0, 1, 1) self.spinBox_voltage = QtWidgets.QSpinBox(self.frame) self.spinBox_voltage.setMaximum(1000000) self.spinBox_voltage.setProperty("value", 30000) self.spinBox_voltage.setObjectName("spinBox_voltage") self.gridLayout_4.addWidget(self.spinBox_voltage, 6, 1, 1, 1) - self.label_preset = QtWidgets.QLabel(self.frame) - self.label_preset.setObjectName("label_preset") - self.gridLayout_4.addWidget(self.label_preset, 14, 0, 1, 1) - self.label_application_file = QtWidgets.QLabel(self.frame) - self.label_application_file.setObjectName("label_application_file") - self.gridLayout_4.addWidget(self.label_application_file, 9, 0, 1, 1) - self.label_dwell_time = QtWidgets.QLabel(self.frame) - self.label_dwell_time.setObjectName("label_dwell_time") - self.gridLayout_4.addWidget(self.label_dwell_time, 11, 0, 1, 1) - self.label_milling_stage = QtWidgets.QLabel(self.frame) - self.label_milling_stage.setObjectName("label_milling_stage") - self.gridLayout_4.addWidget(self.label_milling_stage, 2, 0, 1, 1) - self.doubleSpinBox_hfw = QtWidgets.QDoubleSpinBox(self.frame) - self.doubleSpinBox_hfw.setEnabled(True) - self.doubleSpinBox_hfw.setReadOnly(True) - self.doubleSpinBox_hfw.setMinimum(10.0) - self.doubleSpinBox_hfw.setMaximum(1000000000.0) - self.doubleSpinBox_hfw.setProperty("value", 150.0) - self.doubleSpinBox_hfw.setObjectName("doubleSpinBox_hfw") - self.gridLayout_4.addWidget(self.doubleSpinBox_hfw, 8, 1, 1, 1) + self.label_rate = QtWidgets.QLabel(self.frame) + self.label_rate.setObjectName("label_rate") + self.gridLayout_4.addWidget(self.label_rate, 10, 0, 1, 1) self.line_2 = QtWidgets.QFrame(self.frame) self.line_2.setFrameShape(QtWidgets.QFrame.HLine) self.line_2.setFrameShadow(QtWidgets.QFrame.Sunken) self.line_2.setObjectName("line_2") - self.gridLayout_4.addWidget(self.line_2, 16, 0, 1, 2) - self.doubleSpinBox_milling_current = QtWidgets.QDoubleSpinBox(self.frame) - self.doubleSpinBox_milling_current.setObjectName("doubleSpinBox_milling_current") - self.gridLayout_4.addWidget(self.doubleSpinBox_milling_current, 7, 1, 1, 1) + self.gridLayout_4.addWidget(self.line_2, 18, 0, 1, 2) self.comboBox_preset = QtWidgets.QComboBox(self.frame) self.comboBox_preset.setObjectName("comboBox_preset") self.gridLayout_4.addWidget(self.comboBox_preset, 14, 1, 1, 1) - self.label_rate = QtWidgets.QLabel(self.frame) - self.label_rate.setObjectName("label_rate") - self.gridLayout_4.addWidget(self.label_rate, 10, 0, 1, 1) - self.pushButton = QtWidgets.QPushButton(self.frame) - self.pushButton.setObjectName("pushButton") - self.gridLayout_4.addWidget(self.pushButton, 30, 0, 1, 2) - self.label_milling_current = QtWidgets.QLabel(self.frame) - self.label_milling_current.setObjectName("label_milling_current") - self.gridLayout_4.addWidget(self.label_milling_current, 7, 0, 1, 1) - self.label_patterns_header = QtWidgets.QLabel(self.frame) - font = QtGui.QFont() - font.setPointSize(10) - font.setBold(True) - font.setWeight(75) - self.label_patterns_header.setFont(font) - self.label_patterns_header.setObjectName("label_patterns_header") - self.gridLayout_4.addWidget(self.label_patterns_header, 17, 0, 1, 2) self.label_spacing = QtWidgets.QLabel(self.frame) self.label_spacing.setObjectName("label_spacing") self.gridLayout_4.addWidget(self.label_spacing, 13, 0, 1, 1) + self.label_centre_x = QtWidgets.QLabel(self.frame) + self.label_centre_x.setObjectName("label_centre_x") + self.gridLayout_4.addWidget(self.label_centre_x, 23, 0, 1, 1) + spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout_4.addItem(spacerItem, 28, 0, 1, 2) + self.label_preset = QtWidgets.QLabel(self.frame) + self.label_preset.setObjectName("label_preset") + self.gridLayout_4.addWidget(self.label_preset, 14, 0, 1, 1) + self.line_3 = QtWidgets.QFrame(self.frame) + self.line_3.setFrameShape(QtWidgets.QFrame.HLine) + self.line_3.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line_3.setObjectName("line_3") + self.gridLayout_4.addWidget(self.line_3, 4, 0, 1, 2) + self.checkBox_live_update = QtWidgets.QCheckBox(self.frame) + self.checkBox_live_update.setObjectName("checkBox_live_update") + self.gridLayout_4.addWidget(self.checkBox_live_update, 29, 0, 1, 1) self.pushButton_add_milling_stage = QtWidgets.QPushButton(self.frame) self.pushButton_add_milling_stage.setObjectName("pushButton_add_milling_stage") self.gridLayout_4.addWidget(self.pushButton_add_milling_stage, 3, 0, 1, 1) + self.doubleSpinBox_spacing = QtWidgets.QDoubleSpinBox(self.frame) + self.doubleSpinBox_spacing.setProperty("value", 1.0) + self.doubleSpinBox_spacing.setObjectName("doubleSpinBox_spacing") + self.gridLayout_4.addWidget(self.doubleSpinBox_spacing, 13, 1, 1, 1) + self.label_spot_size = QtWidgets.QLabel(self.frame) + self.label_spot_size.setObjectName("label_spot_size") + self.gridLayout_4.addWidget(self.label_spot_size, 12, 0, 1, 1) self.checkBox_show_milling_crosshair = QtWidgets.QCheckBox(self.frame) self.checkBox_show_milling_crosshair.setChecked(True) self.checkBox_show_milling_crosshair.setObjectName("checkBox_show_milling_crosshair") - self.gridLayout_4.addWidget(self.checkBox_show_milling_crosshair, 28, 1, 1, 1) + self.gridLayout_4.addWidget(self.checkBox_show_milling_crosshair, 30, 1, 1, 1) + self.label_milling_drift_correction_interval = QtWidgets.QLabel(self.frame) + self.label_milling_drift_correction_interval.setObjectName("label_milling_drift_correction_interval") + self.gridLayout_4.addWidget(self.label_milling_drift_correction_interval, 16, 0, 1, 1) + self.spinBox_milling_drift_correction_interval = QtWidgets.QSpinBox(self.frame) + self.spinBox_milling_drift_correction_interval.setObjectName("spinBox_milling_drift_correction_interval") + self.gridLayout_4.addWidget(self.spinBox_milling_drift_correction_interval, 16, 1, 1, 1) self.gridLayout.addWidget(self.frame, 16, 0, 1, 2) spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) self.gridLayout.addItem(spacerItem1, 29, 0, 1, 2) @@ -227,25 +239,28 @@ def retranslateUi(self, Form): _translate = QtCore.QCoreApplication.translate Form.setWindowTitle(_translate("Form", "Form")) self.pushButton_run_milling.setText(_translate("Form", "Run Milling")) + self.label_milling_header.setText(_translate("Form", "Milling")) self.pushButton_remove_milling_stage.setText(_translate("Form", "Remove")) - self.label_centre_x.setText(_translate("Form", "Centre X (um)")) - self.label_hfw.setText(_translate("Form", "Horizontal Field Width (um)")) - self.checkBox_relative_move.setText(_translate("Form", "Keep Relative Orientation")) - self.checkBox_live_update.setText(_translate("Form", "Live Update")) - self.label_centre_y.setText(_translate("Form", "Centre Y (um)")) self.label_voltage.setText(_translate("Form", "Voltage (V)")) + self.label_milling_current.setText(_translate("Form", "Current (nA)")) + self.checkBox_relative_move.setText(_translate("Form", "Keep Relative Orientation")) self.label_pattern_set.setText(_translate("Form", "Pattern")) - self.label_milling_header.setText(_translate("Form", "Milling")) - self.label_milling_instructions.setText(_translate("Form", "Controls:")) - self.label_spot_size.setText(_translate("Form", "Spot Size (um)")) - self.label_preset.setText(_translate("Form", "Preset")) self.label_application_file.setText(_translate("Form", "Application File")) - self.label_dwell_time.setText(_translate("Form", "Dwell Time (us)")) + self.label_milling_instructions.setText(_translate("Form", "Controls:")) self.label_milling_stage.setText(_translate("Form", "Milling Stage")) - self.label_rate.setText(_translate("Form", "Rate (mm3/A/s)")) - self.pushButton.setText(_translate("Form", "Update Pattern")) - self.label_milling_current.setText(_translate("Form", "Current (nA)")) self.label_patterns_header.setText(_translate("Form", "Patterns")) + self.pushButton.setText(_translate("Form", "Update Pattern")) + self.pushButton_edit_drift_correction_area.setText(_translate("Form", "Edit Alignment Area")) + self.checkBox_milling_drift_correction.setText(_translate("Form", "Drift Correction")) + self.label_dwell_time.setText(_translate("Form", "Dwell Time (us)")) + self.label_hfw.setText(_translate("Form", "Horizontal Field Width (um)")) + self.label_centre_y.setText(_translate("Form", "Centre Y (um)")) + self.label_rate.setText(_translate("Form", "Rate (mm3/A/s)")) self.label_spacing.setText(_translate("Form", "Spacing")) + self.label_centre_x.setText(_translate("Form", "Centre X (um)")) + self.label_preset.setText(_translate("Form", "Preset")) + self.checkBox_live_update.setText(_translate("Form", "Live Update")) self.pushButton_add_milling_stage.setText(_translate("Form", "Add")) + self.label_spot_size.setText(_translate("Form", "Spot Size (um)")) self.checkBox_show_milling_crosshair.setText(_translate("Form", "Show Milling Crosshair")) + self.label_milling_drift_correction_interval.setText(_translate("Form", "Drift Correction Interval (s)")) diff --git a/fibsem/ui/qtdesigner_files/FibsemMillingWidget.ui b/fibsem/ui/qtdesigner_files/FibsemMillingWidget.ui index c0ea57fb..6a0421cb 100644 --- a/fibsem/ui/qtdesigner_files/FibsemMillingWidget.ui +++ b/fibsem/ui/qtdesigner_files/FibsemMillingWidget.ui @@ -30,19 +30,17 @@ - - - - - - - -10000000000000000.000000000000000 - - - 99999999999999991611392.000000000000000 + + + + + 10 + 75 + true + - - 0.100000000000000 + + Milling @@ -53,27 +51,30 @@ - - + + - Centre X (um) + Voltage (V) - - + + - Horizontal Field Width (um) + Current (nA) - - - - Keep Relative Orientation + + + + -9999999999999999583119736832.000000000000000 - - true + + 1000000000000000000.000000000000000 + + + 0.100000000000000 @@ -96,110 +97,71 @@ - - + + - Live Update - - - - - - - 4 - - - 0.000000000000000 - - - 100000.000000000000000 - - - 0.010000000000000 + Keep Relative Orientation - - 0.000000000000000 + + true - - - - 4 - - - 0.000000000000000 - - - 100000.000000000000000 - - - 0.010000000000000 - - - 0.000000000000000 + + + + Pattern - - + + - - + + + + + - Centre Y (um) + Application File - - + + - Voltage (V) + Controls: - - - - 1.000000000000000 + + + + Milling Stage - - - - - - - Qt::Horizontal + + + + true - - - - - - Qt::Vertical + + true - - - 20 - 40 - + + 10.000000000000000 - - - - - - Pattern + + 1000000000.000000000000000 + + + 150.000000000000000 - - - - - + + 10 @@ -208,58 +170,66 @@ - Milling + Patterns - - + + - Controls: + Update Pattern - - - -9999999999999999583119736832.000000000000000 - - - 1000000000000000000.000000000000000 - - - 0.100000000000000 - - + - - + + - Spot Size (um) + Edit Alignment Area - - + + + + + + + 4 + + + 0.000000000000000 + - 1000000 + 100000.000000000000000 + + + 0.010000000000000 - 30000 + 0.000000000000000 - - + + - Preset + Drift Correction - - - - Application File + + + + -10000000000000000.000000000000000 + + + 99999999999999991611392.000000000000000 + + + 0.100000000000000 @@ -270,44 +240,51 @@ - - + + + + + - Milling Stage + Horizontal Field Width (um) - - - - true - - - true + + + + 4 - 10.000000000000000 + 0.000000000000000 - 1000000000.000000000000000 + 100000.000000000000000 + + + 0.010000000000000 - 150.000000000000000 + 0.000000000000000 - - - - Qt::Horizontal + + + + Centre Y (um) - - - - - + + + + 1000000 + + + 30000 + + @@ -316,38 +293,61 @@ - - + + + + Qt::Horizontal + + + + + + + + - Update Pattern + Spacing - - + + - Current (nA) + Centre X (um) - - - - - 10 - 75 - true - + + + + Qt::Vertical + + + + 20 + 40 + + + + + - Patterns + Preset - - + + + + Qt::Horizontal + + + + + - Spacing + Live Update @@ -358,7 +358,21 @@ - + + + + 1.000000000000000 + + + + + + + Spot Size (um) + + + + Show Milling Crosshair @@ -368,6 +382,16 @@ + + + + Drift Correction Interval (s) + + + + + + diff --git a/fibsem/ui/utils.py b/fibsem/ui/utils.py index 560964ef..11c29fb5 100644 --- a/fibsem/ui/utils.py +++ b/fibsem/ui/utils.py @@ -401,7 +401,7 @@ def _remove_all_layers(viewer: napari.Viewer, layer_type = napari.layers.shapes. # remove all shapes layers layers_to_remove = [] - layers_to_ignore = ["ruler_line","crosshair","scalebar","scalebar_value", "label"] + _ignore + layers_to_ignore = ["ruler_line","crosshair","scalebar","scalebar_value", "label", "alignment_area"] + _ignore for layer in viewer.layers: if layer.name in layers_to_ignore: @@ -722,7 +722,7 @@ def convert_point_to_napari(resolution: list, pixel_size: float, centre: Point): return Point(cx, cy) - +# TODO: redo this function def validate_pattern_placement( patterns: list[FibsemPatternSettings], resolution: list, shape: list[list[float]] ):