diff --git a/software/control/_def.py b/software/control/_def.py index c7a70db69..fe3c5ca0c 100644 --- a/software/control/_def.py +++ b/software/control/_def.py @@ -429,7 +429,8 @@ class SOFTWARE_POS_LIMIT: DEFAULT_MULTIPOINT_NY=1 ENABLE_FLEXIBLE_MULTIPOINT = True -ENABLE_SCAN_GRID = True +USE_OVERLAP_FOR_FLEXIBLE = False +ENABLE_WELLPLATE_MULTIPOINT = True ENABLE_RECORDING = False CAMERA_SN = {'ch 1':'SN1','ch 2': 'SN2'} # for multiple cameras, to be overwritten in the configuration file diff --git a/software/control/core.py b/software/control/core.py index f0c314b95..7a8542483 100644 --- a/software/control/core.py +++ b/software/control/core.py @@ -1803,12 +1803,7 @@ def run_single_time_point(self): # init z parameters, z range self.initialize_z_stack() - if self.coordinate_dict is not None: - print("coordinate acquisition") - self.run_coordinate_acquisition(current_path) - else: - print("grid acquisition") - self.run_grid_acquisition(current_path) + self.run_coordinate_acquisition(current_path) # finished region scan self.coordinates_pd.to_csv(os.path.join(current_path,'coordinates.csv'),index=False,header=True) @@ -1847,13 +1842,10 @@ def initialize_coordinates_dataframe(self): base_columns = ['z_level', 'x (mm)', 'y (mm)', 'z (um)', 'time'] piezo_column = ['z_piezo (um)'] if self.use_piezo else [] - if IS_HCS: - if self.coordinate_dict is not None: - self.coordinates_pd = pd.DataFrame(columns=['region', 'fov'] + base_columns + piezo_column) - else: - self.coordinates_pd = pd.DataFrame(columns=['region', 'i', 'j'] + base_columns + piezo_column) + if self.coordinate_dict is not None: + self.coordinates_pd = pd.DataFrame(columns=['region', 'fov'] + base_columns + piezo_column) else: - self.coordinates_pd = pd.DataFrame(columns=['i', 'j'] + base_columns + piezo_column) + self.coordinates_pd = pd.DataFrame(columns=['region', 'i', 'j'] + base_columns + piezo_column) def update_coordinates_dataframe(self, region_id, z_level, fov=None, i=None, j=None): base_data = { @@ -1889,17 +1881,6 @@ def update_coordinates_dataframe(self, region_id, z_level, fov=None, i=None, j=N self.coordinates_pd = pd.concat([self.coordinates_pd, new_row], ignore_index=True) - def calculate_grid_indices(self, i, j): - # Ensure that i/y-indexing is always top to bottom - sgn_i = -1 if self.deltaY >= 0 else 1 - sgn_i = -sgn_i if INVERTED_OBJECTIVE else sgn_i - sgn_j = self.x_scan_direction if self.deltaX >= 0 else -self.x_scan_direction - - real_i = self.NY-1-i if sgn_i == -1 else i - real_j = self.NX-1-j if sgn_j == -1 else j - - return sgn_i, sgn_j, real_i, real_j - def move_to_coordinate(self, coordinate_mm): print("moving to coordinate", coordinate_mm) @@ -1935,60 +1916,6 @@ def move_to_z_level(self, z_mm): self.wait_till_operation_is_completed() time.sleep(SCAN_STABILIZATION_TIME_MS_Z/1000) - def run_grid_acquisition(self, current_path): - n_regions = len(self.scan_coordinates_mm) - - for region_id in range(n_regions): - self.signal_acquisition_progress.emit(region_id + 1, n_regions, self.time_point) - coordinate_mm = self.scan_coordinates_mm[region_id] - - self.x_scan_direction = 1 - self.dx_usteps = 0 # accumulated x displacement - self.dy_usteps = 0 # accumulated y displacement - - if self.use_scan_coordinates: - # Calculate grid size - grid_size_x_mm = (self.NX - 1) * self.deltaX - grid_size_y_mm = (self.NY - 1) * self.deltaY - - # Calculate top-left corner position - start_x = coordinate_mm[0] - grid_size_x_mm / 2 - start_y = coordinate_mm[1] - grid_size_y_mm / 2 - if len(coordinate_mm) == 3: - self.move_to_coordinate([start_x, start_y, coordinate_mm[2]]) - else: - self.move_to_coordinate([start_x, start_y]) - - self.wait_till_operation_is_completed() - - self.num_fovs = self.NX * self.NY - len(self.multiPointController.scanCoordinates.grid_skip_positions) - self.total_scans = self.num_fovs * self.NZ * len(self.selected_configurations) - fov_count = 0 # count fovs for progress - - for i in range(self.NY): - self.af_fov_count = 0 # for AF, so that AF at the beginning of each new row - - for j in range(self.NX): - sgn_i, sgn_j, real_i, real_j = self.calculate_grid_indices(i, j) - - if not self.multiPointController.scanCoordinates or (real_i, real_j) not in self.multiPointController.scanCoordinates.grid_skip_positions: - self.acquire_at_position(region_id, current_path, fov_count, i=real_i, j=real_j) - fov_count += 1 - - if self.multiPointController.abort_acqusition_requested: - self.handle_acquisition_abort(current_path, region_id) - return - - if j < self.NX - 1: - self.move_to_next_x_position() - - if i < self.NY - 1: - self.move_to_next_y_position() - - self.x_scan_direction = -self.x_scan_direction - - self.finish_grid_scan(n_regions, region_id) - def run_coordinate_acquisition(self, current_path): n_regions = len(self.scan_coordinates_mm) @@ -2145,15 +2072,15 @@ def perform_autofocus(self, region_id): self.scan_coordinates_mm[region_id][2] = self.navigationController.z_pos_mm # update the coordinate in the widget if self.coordinate_dict is not None: - self.microscope.multiPointWidgetGrid.update_region_z_level(region_id, self.navigationController.z_pos_mm) + self.microscope.wellplateMultiPointWidget.update_region_z_level(region_id, self.navigationController.z_pos_mm) elif self.multiPointController.location_list is not None: try: - self.microscope.multiPointWidget2._update_z(region_id, self.navigationController.z_pos_mm) + self.microscope.flexibleMultiPointWidget._update_z(region_id, self.navigationController.z_pos_mm) except: print("failed update flexible widget z") pass try: - self.microscope.multiPointWidgetGrid.update_region_z_level(region_id, self.navigationController.z_pos_mm) + self.microscope.wellplateMultiPointWidget.update_region_z_level(region_id, self.navigationController.z_pos_mm) except: print("failed update grid widget z") pass @@ -2451,18 +2378,6 @@ def handle_acquisition_abort(self, current_path, region_id=0): self.coordinates_pd.to_csv(os.path.join(current_path,'coordinates.csv'),index=False,header=True) self.navigationController.enable_joystick_button_action = True - def move_to_next_x_position(self): - self.navigationController.move_x_usteps(self.x_scan_direction*self.deltaX_usteps) - self.wait_till_operation_is_completed() - time.sleep(SCAN_STABILIZATION_TIME_MS_X/1000) - self.dx_usteps = self.dx_usteps + self.x_scan_direction*self.deltaX_usteps - - def move_to_next_y_position(self): - self.navigationController.move_y_usteps(self.deltaY_usteps) - self.wait_till_operation_is_completed() - time.sleep(SCAN_STABILIZATION_TIME_MS_Y/1000) - self.dy_usteps = self.dy_usteps + self.deltaY_usteps - def move_z_for_stack(self): if self.use_piezo: self.z_piezo_um += self.deltaZ*1000 @@ -2510,40 +2425,6 @@ def move_z_back_after_stack(self): self.wait_till_operation_is_completed() self.dz_usteps = self.dz_usteps - self.deltaZ_usteps*(self.NZ-1) - def finish_grid_scan(self, n_regions, region_id): - print("moving slide back") - if SHOW_TILED_PREVIEW and IS_HCS: - self.navigationController.keep_scan_begin_position(self.navigationController.x_pos_mm, self.navigationController.y_pos_mm) - - if n_regions == 1: - # only move to the start position if there's only one region in the scan - if self.NY > 1: - # move y back - self.navigationController.move_y_usteps(-self.deltaY_usteps*(self.NY-1)) - self.wait_till_operation_is_completed() - time.sleep(SCAN_STABILIZATION_TIME_MS_Y/1000) - self.dy_usteps = self.dy_usteps - self.deltaY_usteps*(self.NY-1) - - if SHOW_TILED_PREVIEW and not IS_HCS: - self.navigationController.keep_scan_begin_position(self.navigationController.x_pos_mm, self.navigationController.y_pos_mm) - - # move x back at the end of the scan - if self.x_scan_direction == -1: - self.navigationController.move_x_usteps(-self.deltaX_usteps*(self.NX-1)) - self.wait_till_operation_is_completed() - time.sleep(SCAN_STABILIZATION_TIME_MS_X/1000) - - # move z back - if self.navigationController.get_pid_control_flag(2) is False: - _usteps_to_clear_backlash = max(160,20*self.navigationController.z_microstepping) - self.navigationController.move_z_to_usteps(self.z_pos - STAGE_MOVEMENT_SIGN_Z*_usteps_to_clear_backlash) - self.wait_till_operation_is_completed() - self.navigationController.move_z_usteps(_usteps_to_clear_backlash) - self.wait_till_operation_is_completed() - else: - self.navigationController.microcontroller.move_z_to_usteps(self.z_pos) - self.wait_till_operation_is_completed() - def update_tiled_preview(self, current_round_images, i, j, k): if SHOW_TILED_PREVIEW and 'BF LED matrix full' in current_round_images: # initialize the variable @@ -2757,37 +2638,21 @@ def set_selected_configurations(self, selected_configurations_name): self.selected_configurations.append(next((config for config in self.configurationManager.configurations if config.name == configuration_name))) def run_acquisition(self, location_list=None, coordinate_dict=None): + # location_list dict -> {key: region_id, value: center coordinate (x,y,z)} + # coordinate_dict dict -> {key: region_id, value: fov coordinates list [(x0,y0,z0), (x1,y1,z1), ... ]} print('start multipoint') if coordinate_dict is not None: - print('Using coordinate-based acquisition') + print('Using coordinate-based acquisition') # always total_points = sum(len(coords) for coords in coordinate_dict) self.coordinate_dict = coordinate_dict self.location_list = None self.use_scan_coordinates = False self.scan_coordinates_mm = location_list self.scan_coordinates_name = list(coordinate_dict.keys()) # list(coordinate_dict.keys()) if not wellplate - elif location_list is not None: - print('Using location list acquisition') - self.coordinate_dict = None - self.location_list = location_list - self.use_scan_coordinates = True - self.scan_coordinates_mm = location_list - self.scan_coordinates_name = [f'R{i}' for i in range(len(location_list))] else: - print(f"t_c_z_y_x: {self.Nt}_{len(self.selected_configurations)}_{self.NZ}_{self.NY}_{self.NX}") - self.coordinate_dict = None - self.location_list = None - if self.scanCoordinates is not None and self.scanCoordinates.get_selected_wells(): - print('Using well plate scan') - self.use_scan_coordinates = True - self.scan_coordinates_mm = self.scanCoordinates.coordinates_mm - self.scan_coordinates_name = self.scanCoordinates.name - else: - print('Using current location') - self.use_scan_coordinates = False - self.scan_coordinates_mm = [(self.navigationController.x_pos_mm, self.navigationController.y_pos_mm)] - self.scan_coordinates_name = ['ROI'] + print("obsolete functionailty. use coordinate acquisition instead of grid acquisition") + return print("num regions:",len(self.scan_coordinates_mm)) print("region ids:", self.scan_coordinates_name) @@ -4153,7 +4018,6 @@ def __init__(self): self.wellplate_offset_y_mm = WELLPLATE_OFFSET_Y_mm self.well_spacing_mm = WELL_SPACING_MM self.well_size_mm = WELL_SIZE_MM - self.grid_skip_positions = [] def _index_to_row(self,index): index += 1 diff --git a/software/control/gui_hcs.py b/software/control/gui_hcs.py index e890075b1..02548a24e 100644 --- a/software/control/gui_hcs.py +++ b/software/control/gui_hcs.py @@ -114,9 +114,9 @@ def __init__(self, is_simulation=False, live_only_mode=False, *args, **kwargs): if HOMING_ENABLED_X and HOMING_ENABLED_Y and HOMING_ENABLED_Z: self.navigationController.move_to_cached_position() self.waitForMicrocontroller() - if ENABLE_SCAN_GRID: - self.multiPointWidgetGrid.init_z() - self.multiPointWidget2.init_z() + if ENABLE_WELLPLATE_MULTIPOINT: + self.wellplateMultiPointWidget.init_z() + self.flexibleMultiPointWidget.init_z() # Create the menu bar menubar = self.menuBar() @@ -423,9 +423,8 @@ def loadWidgets(self): else: self.setupImageDisplayTabs() - self.multiPointWidget = widgets.MultiPointWidget(self.multipointController, self.configurationManager) - self.multiPointWidget2 = widgets.MultiPointWidget2(self.navigationController, self.navigationViewer, self.multipointController, self.configurationManager, scanCoordinates=None) - self.multiPointWidgetGrid = widgets.MultiPointWidgetGrid(self.navigationController, self.navigationViewer, self.multipointController, self.objectiveStore, self.configurationManager, self.scanCoordinates, self.napariMosaicDisplayWidget) + self.flexibleMultiPointWidget = widgets.FlexibleMultiPointWidget(self.navigationController, self.navigationViewer, self.multipointController, self.objectiveStore, self.configurationManager, scanCoordinates=None) + self.wellplateMultiPointWidget = widgets.WellplateMultiPointWidget(self.navigationController, self.navigationViewer, self.multipointController, self.objectiveStore, self.configurationManager, self.scanCoordinates, self.napariMosaicDisplayWidget) self.sampleSettingsWidget = widgets.SampleSettingsWidget(self.objectivesWidget, self.wellplateFormatWidget) if ENABLE_TRACKING: @@ -504,10 +503,10 @@ def setupImageDisplayTabs(self): self.imageDisplayTabs.addTab(laserfocus_dockArea, "Laser-Based Focus") def setupRecordTabWidget(self): - if ENABLE_SCAN_GRID: - self.recordTabWidget.addTab(self.multiPointWidgetGrid, "Wellplate Multipoint") + if ENABLE_WELLPLATE_MULTIPOINT: + self.recordTabWidget.addTab(self.wellplateMultiPointWidget, "Wellplate Multipoint") if ENABLE_FLEXIBLE_MULTIPOINT: - self.recordTabWidget.addTab(self.multiPointWidget2, "Flexible Multipoint") + self.recordTabWidget.addTab(self.flexibleMultiPointWidget, "Flexible Multipoint") if ENABLE_TRACKING: self.recordTabWidget.addTab(self.trackingControlWidget, "Tracking") if ENABLE_RECORDING: @@ -633,27 +632,22 @@ def makeConnections(self): else: self.navigationController.signal_joystick_button_pressed.connect(self.autofocusController.autofocus) - self.multiPointWidget.signal_acquisition_started.connect(self.toggleAcquisitionStart) - if ENABLE_STITCHER: self.multipointController.signal_stitcher.connect(self.startStitcher) - self.multiPointWidget.signal_stitcher_widget.connect(self.toggleStitcherWidget) - self.multiPointWidget.signal_acquisition_channels.connect(self.stitcherWidget.updateRegistrationChannels) - self.multiPointWidget.signal_acquisition_z_levels.connect(self.stitcherWidget.updateRegistrationZLevels) if ENABLE_FLEXIBLE_MULTIPOINT: - self.multiPointWidget2.signal_acquisition_started.connect(self.toggleAcquisitionStart) + self.flexibleMultiPointWidget.signal_acquisition_started.connect(self.toggleAcquisitionStart) if ENABLE_STITCHER: - self.multiPointWidget2.signal_stitcher_widget.connect(self.toggleStitcherWidget) - self.multiPointWidget2.signal_acquisition_channels.connect(self.stitcherWidget.updateRegistrationChannels) - self.multiPointWidget2.signal_acquisition_z_levels.connect(self.stitcherWidget.updateRegistrationZLevels) + self.flexibleMultiPointWidget.signal_stitcher_widget.connect(self.toggleStitcherWidget) + self.flexibleMultiPointWidget.signal_acquisition_channels.connect(self.stitcherWidget.updateRegistrationChannels) + self.flexibleMultiPointWidget.signal_stitcher_z_levels.connect(self.stitcherWidget.updateRegistrationZLevels) - if ENABLE_SCAN_GRID: - self.multiPointWidgetGrid.signal_acquisition_started.connect(self.toggleAcquisitionStart) + if ENABLE_WELLPLATE_MULTIPOINT: + self.wellplateMultiPointWidget.signal_acquisition_started.connect(self.toggleAcquisitionStart) if ENABLE_STITCHER: - self.multiPointWidgetGrid.signal_stitcher_widget.connect(self.toggleStitcherWidget) - self.multiPointWidgetGrid.signal_acquisition_channels.connect(self.stitcherWidget.updateRegistrationChannels) - self.multiPointWidgetGrid.signal_acquisition_z_levels.connect(self.stitcherWidget.updateRegistrationZLevels) + self.wellplateMultiPointWidget.signal_stitcher_widget.connect(self.toggleStitcherWidget) + self.wellplateMultiPointWidget.signal_acquisition_channels.connect(self.stitcherWidget.updateRegistrationChannels) + self.wellplateMultiPointWidget.signal_stitcher_z_levels.connect(self.stitcherWidget.updateRegistrationZLevels) self.liveControlWidget.signal_newExposureTime.connect(self.cameraSettingWidget.set_exposure_time) self.liveControlWidget.signal_newAnalogGain.connect(self.cameraSettingWidget.set_analog_gain) @@ -662,17 +656,19 @@ def makeConnections(self): self.liveControlWidget.update_camera_settings() self.slidePositionController.signal_slide_loading_position_reached.connect(self.navigationWidget.slot_slide_loading_position_reached) - self.slidePositionController.signal_slide_loading_position_reached.connect(self.multiPointWidget.disable_the_start_aquisition_button) + #self.slidePositionController.signal_slide_loading_position_reached.connect(self.multiPointWidget.disable_the_start_aquisition_button) self.slidePositionController.signal_slide_scanning_position_reached.connect(self.navigationWidget.slot_slide_scanning_position_reached) - self.slidePositionController.signal_slide_scanning_position_reached.connect(self.multiPointWidget.enable_the_start_aquisition_button) + #self.slidePositionController.signal_slide_scanning_position_reached.connect(self.multiPointWidget.enable_the_start_aquisition_button) self.slidePositionController.signal_clear_slide.connect(self.navigationViewer.clear_slide) self.navigationViewer.signal_coordinates_clicked.connect(self.navigationController.move_from_click_mosaic) self.objectivesWidget.signal_objective_changed.connect(self.navigationViewer.on_objective_changed) + if ENABLE_FLEXIBLE_MULTIPOINT: + self.objectivesWidget.signal_objective_changed.connect(self.flexibleMultiPointWidget.update_fov_positions) self.navigationController.xyPos.connect(self.navigationViewer.update_current_location) self.multipointController.signal_register_current_fov.connect(self.navigationViewer.register_fov) self.multipointController.signal_current_configuration.connect(self.liveControlWidget.set_microscope_mode) self.multipointController.signal_z_piezo_um.connect(self.piezoWidget.update_displacement_um_display) - self.multiPointWidgetGrid.signal_z_stacking.connect(self.multipointController.set_z_stacking_config) + self.wellplateMultiPointWidget.signal_z_stacking.connect(self.multipointController.set_z_stacking_config) self.recordTabWidget.currentChanged.connect(self.onTabChanged) if not self.live_only_mode: @@ -705,11 +701,11 @@ def makeConnections(self): self.wellplateFormatWidget.signalWellplateSettings.connect(lambda format_, *args: self.onWellplateChanged(format_)) self.wellSelectionWidget.signal_wellSelectedPos.connect(self.navigationController.move_to) - self.wellSelectionWidget.signal_wellSelected.connect(self.multiPointWidget.set_well_selected) - if ENABLE_SCAN_GRID: - self.wellSelectionWidget.signal_wellSelected.connect(self.multiPointWidgetGrid.set_well_coordinates) - self.objectivesWidget.signal_objective_changed.connect(self.multiPointWidgetGrid.update_coordinates) - self.multiPointWidgetGrid.signal_update_navigation_viewer.connect(self.navigationViewer.update_current_location) + #self.wellSelectionWidget.signal_wellSelected.connect(self.multiPointWidget.set_well_selected) + if ENABLE_WELLPLATE_MULTIPOINT: + self.wellSelectionWidget.signal_wellSelected.connect(self.wellplateMultiPointWidget.set_well_coordinates) + self.objectivesWidget.signal_objective_changed.connect(self.wellplateMultiPointWidget.update_coordinates) + self.wellplateMultiPointWidget.signal_update_navigation_viewer.connect(self.navigationViewer.update_current_location) if SUPPORT_LASER_AUTOFOCUS: self.liveControlWidget_focus_camera.signal_newExposureTime.connect(self.cameraSettingWidget_focus_camera.set_exposure_time) @@ -769,21 +765,19 @@ def makeNapariConnections(self): if USE_NAPARI_FOR_MULTIPOINT: self.napari_connections['napariMultiChannelWidget'] = [ (self.multipointController.napari_layers_init, self.napariMultiChannelWidget.initLayers), - (self.multipointController.napari_layers_update, self.napariMultiChannelWidget.updateLayers), - (self.multiPointWidget.signal_acquisition_channels, self.napariMultiChannelWidget.initChannels), - (self.multiPointWidget.signal_acquisition_shape, self.napariMultiChannelWidget.initLayersShape) + (self.multipointController.napari_layers_update, self.napariMultiChannelWidget.updateLayers) ] if ENABLE_FLEXIBLE_MULTIPOINT: self.napari_connections['napariMultiChannelWidget'].extend([ - (self.multiPointWidget2.signal_acquisition_channels, self.napariMultiChannelWidget.initChannels), - (self.multiPointWidget2.signal_acquisition_shape, self.napariMultiChannelWidget.initLayersShape) + (self.flexibleMultiPointWidget.signal_acquisition_channels, self.napariMultiChannelWidget.initChannels), + (self.flexibleMultiPointWidget.signal_acquisition_shape, self.napariMultiChannelWidget.initLayersShape) ]) - if ENABLE_SCAN_GRID: + if ENABLE_WELLPLATE_MULTIPOINT: self.napari_connections['napariMultiChannelWidget'].extend([ - (self.multiPointWidgetGrid.signal_acquisition_channels, self.napariMultiChannelWidget.initChannels), - (self.multiPointWidgetGrid.signal_acquisition_shape, self.napariMultiChannelWidget.initLayersShape) + (self.wellplateMultiPointWidget.signal_acquisition_channels, self.napariMultiChannelWidget.initChannels), + (self.wellplateMultiPointWidget.signal_acquisition_shape, self.napariMultiChannelWidget.initLayersShape) ]) else: self.multipointController.image_to_display_multi.connect(self.imageArrayDisplayWindow.display_image) @@ -793,30 +787,26 @@ def makeNapariConnections(self): self.napari_connections['napariTiledDisplayWidget'] = [ (self.multipointController.napari_layers_init, self.napariTiledDisplayWidget.initLayers), (self.multipointController.napari_layers_update, self.napariTiledDisplayWidget.updateLayers), - (self.multiPointWidget.signal_acquisition_channels, self.napariTiledDisplayWidget.initChannels), - (self.multiPointWidget.signal_acquisition_shape, self.napariTiledDisplayWidget.initLayersShape), (self.napariTiledDisplayWidget.signal_coordinates_clicked, self.navigationController.scan_preview_move_from_click) ] if ENABLE_FLEXIBLE_MULTIPOINT: self.napari_connections['napariTiledDisplayWidget'].extend([ - (self.multiPointWidget2.signal_acquisition_channels, self.napariTiledDisplayWidget.initChannels), - (self.multiPointWidget2.signal_acquisition_shape, self.napariTiledDisplayWidget.initLayersShape) + (self.flexibleMultiPointWidget.signal_acquisition_channels, self.napariTiledDisplayWidget.initChannels), + (self.flexibleMultiPointWidget.signal_acquisition_shape, self.napariTiledDisplayWidget.initLayersShape) ]) - if ENABLE_SCAN_GRID: + if ENABLE_WELLPLATE_MULTIPOINT: self.napari_connections['napariTiledDisplayWidget'].extend([ - (self.multiPointWidgetGrid.signal_acquisition_channels, self.napariTiledDisplayWidget.initChannels), - (self.multiPointWidgetGrid.signal_acquisition_shape, self.napariTiledDisplayWidget.initLayersShape) + (self.wellplateMultiPointWidget.signal_acquisition_channels, self.napariTiledDisplayWidget.initChannels), + (self.wellplateMultiPointWidget.signal_acquisition_shape, self.napariTiledDisplayWidget.initLayersShape) ]) # Setup mosaic display widget connections if USE_NAPARI_FOR_MOSAIC_DISPLAY: self.napari_connections['napariMosaicDisplayWidget'] = [ (self.multipointController.napari_mosaic_update, self.napariMosaicDisplayWidget.updateMosaic), - (self.multiPointWidget.signal_acquisition_channels, self.napariMosaicDisplayWidget.initChannels), - (self.multiPointWidget.signal_acquisition_shape, self.napariMosaicDisplayWidget.initLayersShape), (self.napariMosaicDisplayWidget.signal_coordinates_clicked, self.navigationController.move_from_click_mosaic), (self.napariMosaicDisplayWidget.signal_update_viewer, self.navigationViewer.update_slide) @@ -824,16 +814,16 @@ def makeNapariConnections(self): if ENABLE_FLEXIBLE_MULTIPOINT: self.napari_connections['napariMosaicDisplayWidget'].extend([ - (self.multiPointWidget2.signal_acquisition_channels, self.napariMosaicDisplayWidget.initChannels), - (self.multiPointWidget2.signal_acquisition_shape, self.napariMosaicDisplayWidget.initLayersShape) + (self.flexibleMultiPointWidget.signal_acquisition_channels, self.napariMosaicDisplayWidget.initChannels), + (self.flexibleMultiPointWidget.signal_acquisition_shape, self.napariMosaicDisplayWidget.initLayersShape) ]) - if ENABLE_SCAN_GRID: + if ENABLE_WELLPLATE_MULTIPOINT: self.napari_connections['napariMosaicDisplayWidget'].extend([ - (self.multiPointWidgetGrid.signal_acquisition_channels, self.napariMosaicDisplayWidget.initChannels), - (self.multiPointWidgetGrid.signal_acquisition_shape, self.napariMosaicDisplayWidget.initLayersShape), - (self.multiPointWidgetGrid.signal_draw_shape, self.napariMosaicDisplayWidget.enable_shape_drawing), - (self.napariMosaicDisplayWidget.signal_shape_drawn, self.multiPointWidgetGrid.update_manual_shape) + (self.wellplateMultiPointWidget.signal_acquisition_channels, self.napariMosaicDisplayWidget.initChannels), + (self.wellplateMultiPointWidget.signal_acquisition_shape, self.napariMosaicDisplayWidget.initLayersShape), + (self.wellplateMultiPointWidget.signal_draw_shape, self.napariMosaicDisplayWidget.enable_shape_drawing), + (self.napariMosaicDisplayWidget.signal_shape_drawn, self.wellplateMultiPointWidget.update_manual_shape) ]) # Make initial connections @@ -887,19 +877,23 @@ def openLedMatrixSettings(self): def onTabChanged(self, index): acquisitionWidget = self.recordTabWidget.widget(index) - is_multipoint = (index == self.recordTabWidget.indexOf(self.multiPointWidget)) - is_scan_grid = (index == self.recordTabWidget.indexOf(self.multiPointWidgetGrid)) if ENABLE_SCAN_GRID else False - self.toggleWellSelector((is_multipoint or is_scan_grid) and self.wellSelectionWidget.format != 'glass slide') + is_flexible = (index == self.recordTabWidget.indexOf(self.flexibleMultiPointWidget)) + is_scan_grid = (index == self.recordTabWidget.indexOf(self.wellplateMultiPointWidget)) if ENABLE_WELLPLATE_MULTIPOINT else False + self.toggleWellSelector(is_scan_grid and self.wellSelectionWidget.format != 'glass slide') + if is_scan_grid: + self.navigationViewer.clear_overlay() self.wellSelectionWidget.onSelectionChanged() else: - self.multiPointWidgetGrid.clear_regions() - try: - if ENABLE_STITCHER: - self.toggleStitcherWidget(acquisitionWidget.checkbox_stitchOutput.isChecked()) - acquisitionWidget.emit_selected_channels() - except AttributeError: - pass + self.wellplateMultiPointWidget.clear_regions() + + if is_flexible: + self.flexibleMultiPointWidget.update_fov_positions() + + if ENABLE_STITCHER: + self.toggleStitcherWidget(acquisitionWidget.checkbox_stitchOutput.isChecked()) + acquisitionWidget.emit_selected_channels() + def resizeCurrentTab(self, tabWidget): current_widget = tabWidget.currentWidget() @@ -937,10 +931,10 @@ def onWellplateChanged(self, format_): self.connectWellSelectionWidget() if ENABLE_FLEXIBLE_MULTIPOINT: - self.multiPointWidget2.clear_only_location_list() - if ENABLE_SCAN_GRID: - self.multiPointWidgetGrid.clear_regions() - self.multiPointWidgetGrid.set_default_scan_size() + self.flexibleMultiPointWidget.clear_only_location_list() + if ENABLE_WELLPLATE_MULTIPOINT: + self.wellplateMultiPointWidget.clear_regions() + self.wellplateMultiPointWidget.set_default_scan_size() self.wellSelectionWidget.onSelectionChanged() def setupSlidePositionController(self, is_for_wellplate): @@ -951,9 +945,9 @@ def setupSlidePositionController(self, is_for_wellplate): def connectSlidePositionController(self): self.slidePositionController.signal_slide_loading_position_reached.connect(self.navigationWidget.slot_slide_loading_position_reached) - self.slidePositionController.signal_slide_loading_position_reached.connect(self.multiPointWidget.disable_the_start_aquisition_button) + #self.slidePositionController.signal_slide_loading_position_reached.connect(self.multiPointWidget.disable_the_start_aquisition_button) self.slidePositionController.signal_slide_scanning_position_reached.connect(self.navigationWidget.slot_slide_scanning_position_reached) - self.slidePositionController.signal_slide_scanning_position_reached.connect(self.multiPointWidget.enable_the_start_aquisition_button) + #self.slidePositionController.signal_slide_scanning_position_reached.connect(self.multiPointWidget.enable_the_start_aquisition_button) self.slidePositionController.signal_clear_slide.connect(self.navigationViewer.clear_slide) if SHOW_NAVIGATION_BAR: self.navigationBarWidget.replace_slide_controller(self.slidePositionController) @@ -970,11 +964,11 @@ def replaceWellSelectionWidget(self, new_widget): self.dock_wellSelection.addWidget(self.wellSelectionWidget) def connectWellSelectionWidget(self): - self.wellSelectionWidget.signal_wellSelected.connect(self.multiPointWidget.set_well_selected) + #self.wellSelectionWidget.signal_wellSelected.connect(self.multiPointWidget.set_well_selected) self.wellSelectionWidget.signal_wellSelectedPos.connect(self.navigationController.move_to) self.wellplateFormatWidget.signalWellplateSettings.connect(self.wellSelectionWidget.updateWellplateSettings) - if ENABLE_SCAN_GRID: - self.wellSelectionWidget.signal_wellSelected.connect(self.multiPointWidgetGrid.set_well_coordinates) + if ENABLE_WELLPLATE_MULTIPOINT: + self.wellSelectionWidget.signal_wellSelected.connect(self.wellplateMultiPointWidget.set_well_coordinates) def toggleWellSelector(self, show): if USE_NAPARI_WELL_SELECTION and not self.performance_mode and not self.live_only_mode: @@ -990,16 +984,13 @@ def toggleAcquisitionStart(self, acquisition_started): self.recordTabWidget.setTabEnabled(index, not acquisition_started or index == current_index) if acquisition_started: self.liveControlWidget.toggle_autolevel(not acquisition_started) - is_multipoint = (current_index == self.recordTabWidget.indexOf(self.multiPointWidget)) - is_scan_grid = (current_index == self.recordTabWidget.indexOf(self.multiPointWidgetGrid)) if ENABLE_SCAN_GRID else False - if (is_multipoint or is_scan_grid) and self.wellSelectionWidget.format != 'glass slide': + + is_scan_grid = (current_index == self.recordTabWidget.indexOf(self.wellplateMultiPointWidget)) if ENABLE_WELLPLATE_MULTIPOINT else False + if is_scan_grid and self.wellSelectionWidget.format != 'glass slide': self.toggleWellSelector(not acquisition_started) - if is_scan_grid: - self.navigationViewer.on_acquisition_start(acquisition_started) - self.multiPointWidgetGrid.display_progress_bar(acquisition_started) - if is_multipoint: - self.navigationViewer.on_acquisition_start(acquisition_started) - self.multiPointWidget2.display_progress_bar(acquisition_started) + + self.recordTabWidget.currentWidget().display_progress_bar(acquisition_started) + self.navigationViewer.on_acquisition_start(acquisition_started) def toggleStitcherWidget(self, checked): if checked: @@ -1017,11 +1008,11 @@ def startStitcher(self, acquisition_path): use_registration = self.stitcherWidget.useRegistrationCheck.isChecked() registration_channel = self.stitcherWidget.registrationChannelCombo.currentText() registration_z_level = self.stitcherWidget.registrationZCombo.value() - overlap_percent = self.multiPointWidgetGrid.entry_overlap.value() + overlap_percent = self.wellplateMultiPointWidget.entry_overlap.value() output_name = acquisitionWidget.lineEdit_experimentID.text() or "stitched" output_format = ".ome.zarr" if self.stitcherWidget.outputFormatCombo.currentText() == "OME-ZARR" else ".ome.tiff" - stitcher_class = stitcher.CoordinateStitcher if self.recordTabWidget.currentIndex() == self.recordTabWidget.indexOf(self.multiPointWidgetGrid) else stitcher.Stitcher + stitcher_class = stitcher.CoordinateStitcher if self.recordTabWidget.currentIndex() == self.recordTabWidget.indexOf(self.wellplateMultiPointWidget) else stitcher.Stitcher self.stitcherThread = stitcher_class( input_folder=acquisition_path, output_name=output_name, diff --git a/software/control/widgets.py b/software/control/widgets.py index 5e4c211a0..44d26f370 100644 --- a/software/control/widgets.py +++ b/software/control/widgets.py @@ -2319,19 +2319,20 @@ def enable_the_start_aquisition_button(self): self.btn_startAcquisition.setEnabled(True) -class MultiPointWidget2(QFrame): +class FlexibleMultiPointWidget(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) + signal_acquisition_started = Signal(bool) # true = started, false = finished + signal_acquisition_channels = Signal(list) # list channels + signal_acquisition_shape = Signal(int, float) # Nz, dz + signal_stitcher_z_levels = Signal(int) # live Nz + signal_stitcher_widget = Signal(bool) # signal start stitcher - def __init__(self, navigationController, navigationViewer, multipointController, configurationManager = None, main=None, scanCoordinates=None, *args, **kwargs): + def __init__(self, navigationController, navigationViewer, multipointController, objectiveStore, configurationManager = None, main=None, scanCoordinates=None, *args, **kwargs): super().__init__(*args, **kwargs) self.last_used_locations = None self.last_used_location_ids = None self.multipointController = multipointController + self.objectiveStore = objectiveStore self.configurationManager = configurationManager self.navigationController = navigationController self.navigationViewer = navigationViewer @@ -2339,9 +2340,12 @@ def __init__(self, navigationController, navigationViewer, multipointController, self.base_path_is_set = False self.location_list = np.empty((0, 3), dtype=float) self.location_ids = np.empty((0,), dtype='= 0: + # Get the region ID + region_id = self.location_ids[index] + print("Before Removal:") + print(f"Location IDs: {self.location_ids}") + print(f"Region FOV Coordinates Dict Keys: {list(self.region_fov_coordinates_dict.keys())}") + + # Remove overlays using actual stored coordinates + if region_id in self.region_fov_coordinates_dict: + for coord in self.region_fov_coordinates_dict[region_id]: + self.navigationViewer.deregister_fov_to_image(coord[0], coord[1]) + del self.region_fov_coordinates_dict[region_id] + + # Remove from data structures + self.location_list = np.delete(self.location_list, index, axis=0) + self.location_ids = np.delete(self.location_ids, index) + if region_id in self.region_coordinates: + del self.region_coordinates[region_id] + + # Update remaining IDs and UI + for i in range(index, len(self.location_ids)): + old_id = self.location_ids[i] + new_id = f'R{i}' + self.location_ids[i] = new_id + + # Update dictionaries + self.region_coordinates[new_id] = self.region_coordinates.pop(old_id) + self.region_fov_coordinates_dict[new_id] = self.region_fov_coordinates_dict.pop(old_id) + + # Update UI with rounded display values + self.table_location_list.setItem(i, 3, QTableWidgetItem(new_id)) + x, y, z = self.location_list[i] + location_str = f"x:{round(x,3)} mm y:{round(y,3)} mm z:{round(z*1000,1)} μm" + self.dropdown_location_list.setItemText(i, location_str) + + # Update UI + self.dropdown_location_list.removeItem(index) + self.table_location_list.removeRow(index) + + print("After Removal:") + print(f"Location IDs: {self.location_ids}") + print(f"Region FOV Coordinates Dict Keys: {list(self.region_fov_coordinates_dict.keys())}") + + # Clear overlay if no locations remain + if len(self.location_list) == 0: + self.navigationViewer.clear_overlay() def create_point_id(self): self.scanCoordinates.get_selected_wells() @@ -3015,21 +3214,6 @@ def create_point_id(self): new_id = f'{name}-0' return new_id - def remove_location(self): - index = self.dropdown_location_list.currentIndex() - if index >=0: - self.dropdown_location_list.removeItem(index) - self.table_location_list.removeRow(index) - x = self.location_list[index,0] - y = self.location_list[index,1] - z = self.location_list[index,2] - self.navigationViewer.deregister_fov_to_image(x,y) - self.location_list = np.delete(self.location_list, index, axis=0) - self.location_ids = np.delete(self.location_ids, index, axis=0) - if len(self.location_list) == 0: - self.navigationViewer.clear_slide() - print(self.location_list) - def next(self): index = self.dropdown_location_list.currentIndex() # max_index = self.dropdown_location_list.count() - 1 @@ -3057,11 +3241,15 @@ def previous(self): def clear(self): self.location_list = np.empty((0, 3), dtype=float) - self.location_ids = np.empty((0,), dtype=str) + self.location_ids = np.empty((0,), dtype=' 1 else scan_size_mm - - else: - # Use grid-based acquisition - if self.scanCoordinates.format == 'glass slide' or len(self.region_coordinates) == 0: - x = self.navigationController.x_pos_mm - y = self.navigationController.y_pos_mm - z = self.navigationController.z_pos_mm - self.region_coordinates['current'] = [x, y, z] - steps, step_size_mm = self.create_scan_grid( + if len(self.region_coordinates) == 0: + # Use current location if no regions added + x = self.navigationController.x_pos_mm + y = self.navigationController.y_pos_mm + z = self.navigationController.z_pos_mm + self.region_coordinates['current'] = [x, y, z] + scan_coordinates = self.create_region_coordinates( self.objectiveStore, + x, y, scan_size_mm=scan_size_mm, overlap_percent=overlap_percent, shape=shape ) - Nx = Ny = steps - dx_mm = dy_mm = step_size_mm + self.region_fov_coordinates_dict['current'] = scan_coordinates - # Set up multipoint controller - self.multipointController.set_NX(Nx) - self.multipointController.set_NY(Ny) - self.multipointController.set_deltaX(dx_mm) - self.multipointController.set_deltaY(dy_mm) + # Calculate total number of positions for signal emission # not needed ever + total_positions = sum(len(coords) for coords in self.region_fov_coordinates_dict.values()) + Nx = Ny = int(math.sqrt(total_positions)) + dx_mm = dy_mm = scan_size_mm / (Nx - 1) if Nx > 1 else scan_size_mm if self.checkbox_set_z_range.isChecked(): # Set Z-range (convert from μm to mm) @@ -4144,17 +4328,11 @@ def toggle_acquisition(self, pressed): # Emit signals self.signal_acquisition_started.emit(True) - self.signal_acquisition_shape.emit(Nx, Ny, self.entry_NZ.value(), - dx_mm, dy_mm, self.entry_deltaZ.value()) + self.signal_acquisition_shape.emit(self.entry_NZ.value(), self.entry_deltaZ.value()) # Start acquisition - if self.use_coordinate_acquisition: - self.multipointController.run_acquisition(location_list=self.region_coordinates, coordinate_dict=self.region_fov_coordinates_dict) - else: - if self.scanCoordinates.format == 'glass slide': - self.multipointController.run_acquisition(location_list=list(self.region_coordinates.values())) # glass slide - else: - self.multipointController.run_acquisition() # wellplate + self.multipointController.run_acquisition(location_list=self.region_coordinates, coordinate_dict=self.region_fov_coordinates_dict) + else: self.multipointController.request_abort_aquisition() self.setEnabled_all(True) @@ -4953,7 +5131,7 @@ def customizeViewer(self): if hasattr(self.viewer.window._qt_viewer, 'layerButtons'): self.viewer.window._qt_viewer.layerButtons.hide() - def initLayersShape(self, Nx, Ny, Nz, dx, dy, dz): + def initLayersShape(self, Nz, dz): pixel_size_um = self.objectiveStore.get_pixel_size() if self.Nz != Nz or self.dz_um != dz or self.pixel_size_um != pixel_size_um: self.acquisition_initialized = False @@ -5403,7 +5581,7 @@ def update_shape_layer_position(self, prev_top_left, new_top_left): def initChannels(self, channels): self.channels = set(channels) - def initLayersShape(self, Nx, Ny, Nz, dx, dy, dz): + def initLayersShape(self, Nz, dz): self.Nz = 1 self.dz_um = dz