diff --git a/software/control/widgets.py b/software/control/widgets.py index 2f07ec39..2e1128d5 100644 --- a/software/control/widgets.py +++ b/software/control/widgets.py @@ -1997,6 +1997,328 @@ def display_stats(self, stats): row+=1 +class MultiPointWidget(QFrame): + + signal_acquisition_started = Signal(bool) + signal_acquisition_channels = Signal(list) + signal_acquisition_z_levels = Signal(int) + signal_acquisition_shape = Signal(int, int, int, float, float, float) + signal_stitcher_widget = Signal(bool) + + def __init__(self, multipointController, configurationManager = None, main=None, *args, **kwargs): + super().__init__(*args, **kwargs) + self.multipointController = multipointController + self.configurationManager = configurationManager + self.well_selected = False + self.base_path_is_set = False + self.add_components() + self.setFrameStyle(QFrame.Panel | QFrame.Raised) + + def add_components(self): + + self.btn_setSavingDir = QPushButton('Browse') + self.btn_setSavingDir.setDefault(False) + self.btn_setSavingDir.setIcon(QIcon('icon/folder.png')) + + self.lineEdit_savingDir = QLineEdit() + self.lineEdit_savingDir.setReadOnly(True) + self.lineEdit_savingDir.setText('Choose a base saving directory') + + self.lineEdit_savingDir.setText(DEFAULT_SAVING_PATH) + self.multipointController.set_base_path(DEFAULT_SAVING_PATH) + self.base_path_is_set = True + + self.lineEdit_experimentID = QLineEdit() + + self.entry_deltaX = QDoubleSpinBox() + self.entry_deltaX.setMinimum(0) + self.entry_deltaX.setMaximum(5) + self.entry_deltaX.setSingleStep(0.1) + self.entry_deltaX.setValue(Acquisition.DX) + self.entry_deltaX.setDecimals(3) + self.entry_deltaX.setSuffix(' mm') + self.entry_deltaX.setKeyboardTracking(False) + + self.entry_NX = QSpinBox() + self.entry_NX.setMinimum(1) + self.entry_NX.setMaximum(50) + self.entry_NX.setSingleStep(1) + self.entry_NX.setValue(Acquisition.NX) + self.entry_NX.setKeyboardTracking(False) + + self.entry_deltaY = QDoubleSpinBox() + self.entry_deltaY.setMinimum(0) + self.entry_deltaY.setMaximum(5) + self.entry_deltaY.setSingleStep(0.1) + self.entry_deltaY.setValue(Acquisition.DX) + self.entry_deltaY.setDecimals(3) + self.entry_deltaY.setSuffix(' mm') + self.entry_deltaY.setKeyboardTracking(False) + + self.entry_NY = QSpinBox() + self.entry_NY.setMinimum(1) + self.entry_NY.setMaximum(50) + self.entry_NY.setSingleStep(1) + self.entry_NY.setValue(Acquisition.NY) + self.entry_NY.setKeyboardTracking(False) + + self.entry_deltaZ = QDoubleSpinBox() + self.entry_deltaZ.setMinimum(0) + self.entry_deltaZ.setMaximum(1000) + self.entry_deltaZ.setSingleStep(0.2) + self.entry_deltaZ.setValue(Acquisition.DZ) + self.entry_deltaZ.setDecimals(3) + self.entry_deltaZ.setSuffix(' μm') + self.entry_deltaZ.setKeyboardTracking(False) + + self.entry_NZ = QSpinBox() + self.entry_NZ.setMinimum(1) + self.entry_NZ.setMaximum(2000) + self.entry_NZ.setSingleStep(1) + self.entry_NZ.setValue(1) + self.entry_NZ.setKeyboardTracking(False) + + self.entry_dt = QDoubleSpinBox() + self.entry_dt.setMinimum(0) + self.entry_dt.setMaximum(12*3600) + self.entry_dt.setSingleStep(1) + self.entry_dt.setValue(0) + self.entry_dt.setSuffix(' s') + self.entry_dt.setKeyboardTracking(False) + + self.entry_Nt = QSpinBox() + self.entry_Nt.setMinimum(1) + self.entry_Nt.setMaximum(5000) # @@@ to be changed + self.entry_Nt.setSingleStep(1) + self.entry_Nt.setValue(1) + self.entry_Nt.setKeyboardTracking(False) + + self.list_configurations = QListWidget() + for microscope_configuration in self.configurationManager.configurations: + self.list_configurations.addItems([microscope_configuration.name]) + self.list_configurations.setSelectionMode(QAbstractItemView.MultiSelection) # ref: https://doc.qt.io/qt-5/qabstractitemview.html#SelectionMode-enum + + self.checkbox_withAutofocus = QCheckBox('Contrast AF') + self.checkbox_withAutofocus.setChecked(MULTIPOINT_CONTRAST_AUTOFOCUS_ENABLE_BY_DEFAULT) + self.multipointController.set_af_flag(MULTIPOINT_CONTRAST_AUTOFOCUS_ENABLE_BY_DEFAULT) + + self.checkbox_genFocusMap = QCheckBox('Focus Map') + self.checkbox_genFocusMap.setChecked(False) + + self.checkbox_withReflectionAutofocus = QCheckBox('Reflection AF') + self.checkbox_withReflectionAutofocus.setChecked(MULTIPOINT_REFLECTION_AUTOFOCUS_ENABLE_BY_DEFAULT) + + self.checkbox_stitchOutput = QCheckBox('Stitch Scans') + self.checkbox_stitchOutput.setChecked(False) + + self.multipointController.set_reflection_af_flag(MULTIPOINT_REFLECTION_AUTOFOCUS_ENABLE_BY_DEFAULT) + + self.btn_startAcquisition = QPushButton('Start\n Acquisition ') + self.btn_startAcquisition.setStyleSheet("background-color: #C2C2FF") + self.btn_startAcquisition.setCheckable(True) + self.btn_startAcquisition.setChecked(False) + + # layout + grid_line0 = QGridLayout() + grid_line0.addWidget(QLabel('Saving Path')) + grid_line0.addWidget(self.lineEdit_savingDir, 0,1) + grid_line0.addWidget(self.btn_setSavingDir, 0,2) + + grid_line1 = QGridLayout() + grid_line1.addWidget(QLabel('Experiment ID'), 0,0) + grid_line1.addWidget(self.lineEdit_experimentID,0,1) + + grid_line2 = QGridLayout() + grid_line2.addWidget(QLabel('dx'), 0,0) + grid_line2.addWidget(self.entry_deltaX, 0,1) + grid_line2.addWidget(QLabel('Nx'), 0,3) + grid_line2.addWidget(self.entry_NX, 0,4) + grid_line2.addWidget(QLabel('dy'), 0,6) + grid_line2.addWidget(self.entry_deltaY, 0,7) + grid_line2.addWidget(QLabel('Ny'), 0,9) + grid_line2.addWidget(self.entry_NY, 0,10) + + grid_line2.addWidget(QLabel('dz'), 1,0) + grid_line2.addWidget(self.entry_deltaZ, 1,1) + grid_line2.addWidget(QLabel('Nz'), 1,3) + grid_line2.addWidget(self.entry_NZ, 1,4) + grid_line2.addWidget(QLabel('dt'), 1,6) + grid_line2.addWidget(self.entry_dt, 1,7) + grid_line2.addWidget(QLabel('Nt'), 1,9) + grid_line2.addWidget(self.entry_Nt, 1,10) + + grid_line2.setColumnStretch(2, 1) + grid_line2.setColumnStretch(5, 1) + grid_line2.setColumnStretch(8, 1) + + grid_af = QGridLayout() + grid_af.addItem(QSpacerItem(7, 1, QSizePolicy.Fixed, QSizePolicy.Minimum), 0, 0) + grid_af.addWidget(self.checkbox_withAutofocus,0,1) + if SUPPORT_LASER_AUTOFOCUS: + grid_af.addWidget(self.checkbox_withReflectionAutofocus,1,1) + grid_af.addWidget(self.checkbox_genFocusMap,2,1) + if ENABLE_STITCHER: + grid_af.addWidget(self.checkbox_stitchOutput,3,1) + grid_af.addItem(QSpacerItem(6, 1, QSizePolicy.Fixed, QSizePolicy.Minimum), 0, 2) + + grid_line3 = QHBoxLayout() + grid_line3.addWidget(self.list_configurations, 2) + # grid_line3.addWidget(self.checkbox_withAutofocus) + grid_line3.addLayout(grid_af, 1) + grid_line3.addWidget(self.btn_startAcquisition, 1) + + self.grid = QGridLayout() + self.grid.addLayout(grid_line0,0,0) + self.grid.addLayout(grid_line1,1,0) + self.grid.addLayout(grid_line2,2,0) + self.grid.addLayout(grid_line3,3,0) + self.setLayout(self.grid) + + # add and display a timer - to be implemented + # self.timer = QTimer() + + # connections + self.entry_deltaX.valueChanged.connect(self.set_deltaX) + self.entry_deltaY.valueChanged.connect(self.set_deltaY) + self.entry_deltaZ.valueChanged.connect(self.set_deltaZ) + self.entry_dt.valueChanged.connect(self.multipointController.set_deltat) + self.entry_NX.valueChanged.connect(self.multipointController.set_NX) + self.entry_NY.valueChanged.connect(self.multipointController.set_NY) + self.entry_NZ.valueChanged.connect(self.multipointController.set_NZ) + self.entry_NZ.valueChanged.connect(self.signal_acquisition_z_levels.emit) + self.entry_Nt.valueChanged.connect(self.multipointController.set_Nt) + self.checkbox_withAutofocus.stateChanged.connect(self.multipointController.set_af_flag) + self.checkbox_withReflectionAutofocus.stateChanged.connect(self.multipointController.set_reflection_af_flag) + self.checkbox_genFocusMap.stateChanged.connect(self.multipointController.set_gen_focus_map_flag) + self.checkbox_stitchOutput.toggled.connect(self.display_stitcher_widget) + self.btn_setSavingDir.clicked.connect(self.set_saving_dir) + self.btn_startAcquisition.clicked.connect(self.toggle_acquisition) + self.multipointController.acquisitionFinished.connect(self.acquisition_is_finished) + self.list_configurations.itemSelectionChanged.connect(self.emit_selected_channels) + + def set_deltaX(self,value): + mm_per_ustep = self.multipointController.navigationController.get_mm_per_ustep_X() + deltaX = round(value/mm_per_ustep)*mm_per_ustep + self.entry_deltaX.setValue(deltaX) + self.multipointController.set_deltaX(deltaX) + + def set_deltaY(self,value): + mm_per_ustep = self.multipointController.navigationController.get_mm_per_ustep_Y() + deltaY = round(value/mm_per_ustep)*mm_per_ustep + self.entry_deltaY.setValue(deltaY) + self.multipointController.set_deltaY(deltaY) + + def set_deltaZ(self,value): + mm_per_ustep = self.multipointController.navigationController.get_mm_per_ustep_Z() + deltaZ = round(value/1000/mm_per_ustep)*mm_per_ustep*1000 + self.entry_deltaZ.setValue(deltaZ) + self.multipointController.set_deltaZ(deltaZ) + + def set_saving_dir(self): + dialog = QFileDialog() + save_dir_base = dialog.getExistingDirectory(None, "Select Folder") + self.multipointController.set_base_path(save_dir_base) + self.lineEdit_savingDir.setText(save_dir_base) + self.base_path_is_set = True + + def set_well_selected(self, selected): + self.well_selected = selected + + def emit_selected_channels(self): + selected_channels = [item.text() for item in self.list_configurations.selectedItems()] + print(selected_channels) + self.signal_acquisition_channels.emit(selected_channels) + + def toggle_acquisition(self,pressed): + if self.base_path_is_set == False: + self.btn_startAcquisition.setChecked(False) + msg = QMessageBox() + msg.setText("Please choose base saving directory first") + msg.exec_() + return + if self.well_selected == False and self.multipointController.scanCoordinates.format != 0: + self.btn_startAcquisition.setChecked(False) + msg = QMessageBox() + msg.setText("Please select a well to scan first") + msg.exec_() + return + if not self.list_configurations.selectedItems(): # no channel selected + self.btn_startAcquisition.setChecked(False) + msg = QMessageBox() + msg.setText("Please select at least one imaging channel first") + msg.exec_() + return + if pressed: + # @@@ to do: add a widgetManger to enable and disable widget + # @@@ to do: emit signal to widgetManager to disable other widgets + self.setEnabled_all(False) + + # set parameters + if self.multipointController.scanCoordinates is not None: + self.multipointController.scanCoordinates.grid_skip_positions = [] + self.multipointController.set_deltaX(self.entry_deltaX.value()) + self.multipointController.set_deltaY(self.entry_deltaY.value()) + self.multipointController.set_deltaZ(self.entry_deltaZ.value()) + self.multipointController.set_deltat(self.entry_dt.value()) + self.multipointController.set_NX(self.entry_NX.value()) + self.multipointController.set_NY(self.entry_NY.value()) + self.multipointController.set_NZ(self.entry_NZ.value()) + self.multipointController.set_Nt(self.entry_Nt.value()) + self.multipointController.set_af_flag(self.checkbox_withAutofocus.isChecked()) + self.multipointController.set_reflection_af_flag(self.checkbox_withReflectionAutofocus.isChecked()) + self.multipointController.set_base_path(self.lineEdit_savingDir.text()) + self.multipointController.set_selected_configurations((item.text() for item in self.list_configurations.selectedItems())) + self.multipointController.start_new_experiment(self.lineEdit_experimentID.text()) + + # emit acquisition data + self.signal_acquisition_started.emit(True) + self.signal_acquisition_shape.emit(self.entry_NX.value(), + self.entry_NY.value(), + self.entry_NZ.value(), + self.entry_deltaX.value(), + self.entry_deltaY.value(), + self.entry_deltaZ.value()) + + self.multipointController.run_acquisition() + else: + self.multipointController.request_abort_aquisition() + self.setEnabled_all(True) + + def acquisition_is_finished(self): + self.signal_acquisition_started.emit(False) + self.btn_startAcquisition.setChecked(False) + self.setEnabled_all(True) + + def setEnabled_all(self,enabled,exclude_btn_startAcquisition=True): + self.btn_setSavingDir.setEnabled(enabled) + self.lineEdit_savingDir.setEnabled(enabled) + self.lineEdit_experimentID.setEnabled(enabled) + self.entry_deltaX.setEnabled(enabled) + self.entry_NX.setEnabled(enabled) + self.entry_deltaY.setEnabled(enabled) + self.entry_NY.setEnabled(enabled) + self.entry_deltaZ.setEnabled(enabled) + self.entry_NZ.setEnabled(enabled) + self.entry_dt.setEnabled(enabled) + self.entry_Nt.setEnabled(enabled) + self.list_configurations.setEnabled(enabled) + self.checkbox_withAutofocus.setEnabled(enabled) + self.checkbox_withReflectionAutofocus.setEnabled(enabled) + self.checkbox_genFocusMap.setEnabled(enabled) + self.checkbox_stitchOutput.setEnabled(enabled) + if exclude_btn_startAcquisition is not True: + self.btn_startAcquisition.setEnabled(enabled) + + def display_stitcher_widget(self, checked): + self.signal_stitcher_widget.emit(checked) + + def disable_the_start_aquisition_button(self): + self.btn_startAcquisition.setEnabled(False) + + def enable_the_start_aquisition_button(self): + self.btn_startAcquisition.setEnabled(True) + + class FlexibleMultiPointWidget(QFrame): signal_acquisition_started = Signal(bool)