def _setup_camera(self): """ Setup camera readout and triggering for OPM :return None: """ with RemoteMMCore() as mmc_camera_setup: # give camera time to change modes if necessary mmc_camera_setup.setConfig('Camera-Setup','ScanMode3') mmc_camera_setup.waitForConfig('Camera-Setup','ScanMode3') # set camera to internal trigger mmc_camera_setup.setConfig('Camera-TriggerType','NORMAL') mmc_camera_setup.waitForConfig('Camera-TriggerType','NORMAL') trigger_value = mmc_camera_setup.getProperty(self.camera_name,'Trigger') while not(trigger_value == 'NORMAL'): mmc_camera_setup.setConfig('Camera-TriggerType','NORMAL') mmc_camera_setup.waitForConfig('Camera-TriggerType','NORMAL') time.sleep(2) trigger_value = mmc_camera_setup.getProperty(self.camera_name,'Trigger') # give camera time to change modes if necessary mmc_camera_setup.setProperty(self.camera_name,r'OUTPUT TRIGGER KIND[0]','EXPOSURE') mmc_camera_setup.setProperty(self.camera_name,r'OUTPUT TRIGGER KIND[1]','EXPOSURE') mmc_camera_setup.setProperty(self.camera_name,r'OUTPUT TRIGGER KIND[2]','EXPOSURE') mmc_camera_setup.setProperty(self.camera_name,r'OUTPUT TRIGGER POLARITY[0]','POSITIVE') mmc_camera_setup.setProperty(self.camera_name,r'OUTPUT TRIGGER POLARITY[1]','POSITIVE') mmc_camera_setup.setProperty(self.camera_name,r'OUTPUT TRIGGER POLARITY[2]','POSITIVE')
def _lasers_to_hardware(self): """ Change lasers to hardware control :return None: """ with RemoteMMCore() as mmc_lasers_hardware: # turn all lasers off mmc_lasers_hardware.setConfig('Laser','Off') mmc_lasers_hardware.waitForConfig('Laser','Off') # set all laser to external triggering mmc_lasers_hardware.setConfig('Modulation-405','External-Digital') mmc_lasers_hardware.waitForConfig('Modulation-405','External-Digital') mmc_lasers_hardware.setConfig('Modulation-488','External-Digital') mmc_lasers_hardware.waitForConfig('Modulation-488','External-Digital') mmc_lasers_hardware.setConfig('Modulation-561','External-Digital') mmc_lasers_hardware.waitForConfig('Modulation-561','External-Digital') mmc_lasers_hardware.setConfig('Modulation-637','External-Digital') mmc_lasers_hardware.waitForConfig('Modulation-637','External-Digital') mmc_lasers_hardware.setConfig('Modulation-730','External-Digital') mmc_lasers_hardware.waitForConfig('Modulation-730','External-Digital') # turn all lasers on mmc_lasers_hardware.setConfig('Laser','AllOn') mmc_lasers_hardware.waitForConfig('Laser','AllOn')
def main(path_to_mm_config=Path( 'C:/Program Files/Micro-Manager-2.0gamma/temp_HamDCAM.cfg')): # launch pymmcore server with RemoteMMCore() as mmc: mmc.loadSystemConfiguration(str(path_to_mm_config)) # create Napari viewer viewer = napari.Viewer( title='ASU OPM control -- iterative multiplexing') # setup OPM widgets iterative_control_widget = OPMIterative() stage_display_widget = OPMStageMonitor() instrument_control_widget = OPMStageScan(iterative_control_widget) instrument_control_widget._set_viewer(viewer) # startup instrument instrument_control_widget._startup() # create thread workers # 2D worker_2d = instrument_control_widget._acquire_2d_data() worker_2d.yielded.connect(instrument_control_widget._update_layers) instrument_control_widget._set_worker_2d(worker_2d) # 3D worker_3d = instrument_control_widget._acquire_3d_data() worker_3d.yielded.connect(instrument_control_widget._update_layers) instrument_control_widget._set_worker_3d(worker_3d) # iterative 3D instrument_control_widget._create_worker_iterative() # instrument setup worker_iterative_setup = iterative_control_widget._return_experiment_setup( ) worker_iterative_setup.returned.connect( instrument_control_widget._set_iterative_configuration) iterative_control_widget._set_worker_iterative_setup( worker_iterative_setup) # add widgets to Napari viewer viewer.window.add_dock_widget(iterative_control_widget, area='bottom', name='Iterative setup') viewer.window.add_dock_widget(stage_display_widget, area='bottom', name='Stage monitor') viewer.window.add_dock_widget(instrument_control_widget, area='right', name='Instrument setup') # start Napari napari.run(max_loop_level=2) worker_2d.quit() worker_3d.quit() instrument_control_widget._shutdown()
def get_stage_pos(self, get_pos_xyz): with RemoteMMCore() as mmc_stage_monitor: x = mmc_stage_monitor.getXPosition() y = mmc_stage_monitor.getYPosition() z = mmc_stage_monitor.getZPosition() self.x_stage_pos.value = (f"{x:.1f}") self.y_stage_pos.value = (f"{y:.1f}") self.z_stage_pos.value = (f"{z:.1f}")
def _set_mmc_laser_power(self): """ Change laser power :return None: """ with RemoteMMCore() as mmc_laser_power: mmc_laser_power.setProperty(r'Coherent-Scientific Remote',r'Laser 405-100C - PowerSetpoint (%)',float(self.channel_powers[0])) mmc_laser_power.setProperty(r'Coherent-Scientific Remote',r'Laser 488-150C - PowerSetpoint (%)',float(self.channel_powers[1])) mmc_laser_power.setProperty(r'Coherent-Scientific Remote',r'Laser OBIS LS 561-150 - PowerSetpoint (%)',float(self.channel_powers[2])) mmc_laser_power.setProperty(r'Coherent-Scientific Remote',r'Laser 637-140C - PowerSetpoint (%)',float(self.channel_powers[3])) mmc_laser_power.setProperty(r'Coherent-Scientific Remote',r'Laser 730-30C - PowerSetpoint (%)',float(self.channel_powers[4]))
def _acquire_2d_data(self): while True: # parse which channels are active active_channel_indices = [ind for ind, st in zip(self.do_ind, self.channel_states) if st] n_active_channels = len(active_channel_indices) if n_active_channels == 0: yield None if self.debug: print("%d active channels: " % n_active_channels, end="") for ind in active_channel_indices: print("%s " % self.channel_labels[ind], end="") print("") if self.powers_changed: self._set_mmc_laser_power() self.powers_changed = False if self.channels_changed: if self.DAQ_running: self.opmdaq.stop_waveform_playback() self.DAQ_running = False self.opmdaq.reset_scan_mirror() self.opmdaq.set_scan_type('stage') self.opmdaq.set_channels_to_use(self.channel_states) self.opmdaq.set_interleave_mode(True) self.opmdaq.generate_waveforms() self.opmdaq.start_waveform_playback() self.DAQ_running=True self.channels_changed = False if not(self.DAQ_running): self.opmdaq.start_waveform_playback() self.DAQ_running=True with RemoteMMCore() as mmc_2d: if self.ROI_changed: self._crop_camera() self.ROI_changed = False # set exposure time if self.exposure_changed: mmc_2d.setExposure(self.exposure_ms) self.exposure_changed = False for c in active_channel_indices: mmc_2d.snapImage() raw_image_2d = mmc_2d.getImage() time.sleep(.05) yield c, raw_image_2d
def _crop_camera(self): """ Crop camera to GUI values :return None: """ with RemoteMMCore() as mmc_crop_camera: current_ROI = mmc_crop_camera.getROI() if not(current_ROI[2]==2304) or not(current_ROI[3]==2304): mmc_crop_camera.clearROI() mmc_crop_camera.waitForDevice(self.camera_name) mmc_crop_camera.setROI(int(self.ROI_uleft_corner_x),int(self.ROI_uleft_corner_y),int(self.ROI_width_x),int(self.ROI_width_y)) mmc_crop_camera.waitForDevice(self.camera_name)
def get_core_singleton(remote=False) -> CMMCorePlus: """Retrieve the MMCore singleton for this session. The first call to this function determines whether we're running remote or not. perhaps a temporary function for now... """ global _SESSION_CORE if _SESSION_CORE is None: if remote: from pymmcore_plus import RemoteMMCore _SESSION_CORE = RemoteMMCore() # type: ignore # it has the same interface. else: _SESSION_CORE = CMMCorePlus.instance() return _SESSION_CORE
def main(path_to_mm_config=Path( 'C:/Program Files/Micro-Manager-2.0gamma/temp_HamDCAM.cfg')): # launch pymmcore server with RemoteMMCore() as mmc: mmc.loadSystemConfiguration( str(path_to_mm_config)) # setup OPM GUI and Napari viewer instrument_control_widget = OPMMirrorScan() # these methods have to be private to not show using magic-class. Maybe a better solution is available? instrument_control_widget._startup() viewer = napari.Viewer( title='ASU Snouty-OPM timelapse acquisition control') # these methods have to be private to not show using magic-class. Maybe a better solution is available? instrument_control_widget._set_viewer(viewer) # setup 2D imaging thread worker # these methods have to be private to not show using magic-class. Maybe a better solution is available? worker_2d = instrument_control_widget._acquire_2d_data() worker_2d.yielded.connect(instrument_control_widget._update_layers) instrument_control_widget._set_worker_2d(worker_2d) # setup 3D imaging thread worker # these methods have to be private to not show using magic-class. Maybe a better solution is available? worker_3d = instrument_control_widget._acquire_3d_data() worker_3d.yielded.connect(instrument_control_widget._update_layers) instrument_control_widget._set_worker_3d(worker_3d) instrument_control_widget._create_3d_t_worker() viewer.window.add_dock_widget(instrument_control_widget, name='Instrument control') # start Napari napari.run(max_loop_level=2) # shutdown acquistion threads worker_2d.quit() worker_3d.quit() # shutdown instrument # these methods have to be private to not show using magic-class. Maybe a better solution is available? instrument_control_widget._shutdown()
def _enforce_DCAM_internal_trigger(self): """ Enforce camera being in trigger = INTERNAL mode :return None: """ with RemoteMMCore() as mmc_camera_trigger: # set camera to START mode upon input trigger mmc_camera_trigger.setConfig('Camera-TriggerSource','INTERNAL') mmc_camera_trigger.waitForConfig('Camera-TriggerSource','INTERNAL') # check if camera actually changed # we find that camera doesn't always go back to START mode and need to check it trigger_value = mmc_camera_trigger.getProperty(self.camera_name,'TRIGGER SOURCE') while not(trigger_value == 'INTERNAL'): mmc_camera_trigger.setConfig('Camera-TriggerSource','INTERNAL') mmc_camera_trigger.waitForConfig('Camera-TriggerSource','INTERNAL') trigger_value = mmc_camera_trigger.getProperty(self.camera_name,'TRIGGER SOURCE')
def _lasers_to_software(self): """ Change lasers to software control :return None: """ with RemoteMMCore() as mmc_lasers_software: # turn all lasers off mmc_lasers_software.setConfig('Laser','Off') mmc_lasers_software.waitForConfig('Laser','Off') # set all lasers back to software control mmc_lasers_software.setConfig('Modulation-405','CW (constant power)') mmc_lasers_software.waitForConfig('Modulation-405','CW (constant power)') mmc_lasers_software.setConfig('Modulation-488','CW (constant power)') mmc_lasers_software.waitForConfig('Modulation-488','CW (constant power)') mmc_lasers_software.setConfig('Modulation-561','CW (constant power)') mmc_lasers_software.waitForConfig('Modulation-561','CW (constant power)') mmc_lasers_software.setConfig('Modulation-637','CW (constant power)') mmc_lasers_software.waitForConfig('Modulation-637','CW (constant power)') mmc_lasers_software.setConfig('Modulation-730','CW (constant power)') mmc_lasers_software.waitForConfig('Modulation-730','CW (constant power)')
from pymmcore_plus import RemoteMMCore with RemoteMMCore(verbose=True) as mmcore: # 'demo' is a special option for the included CMMCorePlus # that loads the micro-manager demo config mmcore.loadSystemConfiguration("demo") print("loaded:", mmcore.getLoadedDevices())
def _acquire_3d_t_data(self): with RemoteMMCore() as mmc_3d_time: #------------------------------------------------------------------------------------------------------------------------------------ #----------------------------------------------Begin setup of scan parameters-------------------------------------------------------- #------------------------------------------------------------------------------------------------------------------------------------ # parse which channels are active active_channel_indices = [ind for ind, st in zip(self.do_ind, self.channel_states) if st] self.n_active_channels = len(active_channel_indices) if self.debug: print("%d active channels: " % self.n_active_channels, end="") for ind in active_channel_indices: print("%s " % self.channel_labels[ind], end="") print("") if self.ROI_changed: self._crop_camera() self.ROI_changed = False # set exposure time if self.exposure_changed: mmc_3d_time.setExposure(self.exposure_ms) self.exposure_changed = False if self.powers_changed: self._set_mmc_laser_power() self.powers_changed = False if self.channels_changed or self.footprint_changed or not(self.DAQ_running): if self.DAQ_running: self.opmdaq.stop_waveform_playback() self.DAQ_running = False self.opmdaq.set_scan_type('mirror') self.opmdaq.set_channels_to_use(self.channel_states) self.opmdaq.set_interleave_mode(True) self.scan_steps = self.opmdaq.set_scan_mirror_range(self.scan_axis_step_um,self.scan_mirror_footprint_um) self.opmdaq.generate_waveforms() self.channels_changed = False self.footprint_changed = False # create directory for timelapse time_string = datetime.now().strftime("%Y_%m_%d-%I_%M_%S") self.output_dir_path = self.save_path / Path('timelapse_'+time_string) self.output_dir_path.mkdir(parents=True, exist_ok=True) # create name for zarr directory zarr_output_path = self.output_dir_path / Path('OPM_data.zarr') # create and open zarr file opm_data = zarr.open(str(zarr_output_path), mode="w", shape=(self.n_timepoints, self.n_active_channels, self.scan_steps, self.ROI_width_y, self.ROI_width_x), chunks=(1, 1, 1, self.ROI_width_y, self.ROI_width_x),compressor=None, dtype=np.uint16) #------------------------------------------------------------------------------------------------------------------------------------ #----------------------------------------------End setup of scan parameters---------------------------------------------------------- #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #----------------------------------------------------Start acquisition--------------------------------------------------------------- #------------------------------------------------------------------------------------------------------------------------------------ # set circular buffer to be large mmc_3d_time.clearCircularBuffer() circ_buffer_mb = 96000 mmc_3d_time.setCircularBufferMemoryFootprint(int(circ_buffer_mb)) # run hardware triggered acquisition if self.wait_time == 0: self.opmdaq.start_waveform_playback() self.DAQ_running = True mmc_3d_time.startSequenceAcquisition(int(self.n_timepoints*self.n_active_channels*self.scan_steps),0,True) for t in trange(self.n_timepoints,desc="t", position=0): for z in trange(self.scan_steps,desc="z", position=1, leave=False): for c in range(self.n_active_channels): while mmc_3d_time.getRemainingImageCount()==0: pass opm_data[t, c, z, :, :] = mmc_3d_time.popNextImage() mmc_3d_time.stopSequenceAcquisition() self.opmdaq.stop_waveform_playback() self.DAQ_running = False else: for t in trange(self.n_timepoints,desc="t", position=0): self.opmdaq.start_waveform_playback() self.DAQ_running = True mmc_3d_time.startSequenceAcquisition(int(self.n_active_channels*self.scan_steps),0,True) for z in trange(self.scan_steps,desc="z", position=1, leave=False): for c in range(self.n_active_channels): while mmc_3d_time.getRemainingImageCount()==0: pass opm_data[t, c, z, :, :] = mmc_3d_time.popNextImage() mmc_3d_time.stopSequenceAcquisition() self.opmdaq.stop_waveform_playback() self.DAQ_running = False time.sleep(self.wait_time) # construct metadata and save self._save_metadata() #------------------------------------------------------------------------------------------------------------------------------------ #--------------------------------------------------------End acquisition------------------------------------------------------------- #------------------------------------------------------------------------------------------------------------------------------------ # set circular buffer to be small mmc_3d_time.clearCircularBuffer() circ_buffer_mb = 4000 mmc_3d_time.setCircularBufferMemoryFootprint(int(circ_buffer_mb))
def _calculate_scan_volume(self): try: with RemoteMMCore() as mmc_stage_setup: # set experiment exposure mmc_stage_setup.setExposure(self.exposure_ms) # snap image mmc_stage_setup.snapImage() # grab exposure true_exposure = mmc_stage_setup.getExposure() # grab ROI current_ROI = mmc_stage_setup.getROI() self.x_pixels = current_ROI[2] self.y_pixels = current_ROI[3] if not ((self.y_pixels == 256) or (self.y_pixels == 512)): raise Exception('Set camera ROI first.') # get actual framerate from micromanager properties actual_readout_ms = true_exposure + float( mmc_stage_setup.getProperty('OrcaFusionBT', 'ReadoutTime')) #unit: ms if self.debug: print('Full readout time = ' + str(actual_readout_ms)) # scan axis setup scan_axis_step_mm = self.scan_axis_step_um / 1000. #unit: mm self.scan_axis_start_mm = self.scan_axis_start_um / 1000. #unit: mm self.scan_axis_end_mm = self.scan_axis_end_um / 1000. #unit: mm scan_axis_range_um = np.abs( self.scan_axis_end_um - self.scan_axis_start_um) # unit: um self.scan_axis_range_mm = scan_axis_range_um / 1000 #unit: mm actual_exposure_s = actual_readout_ms / 1000. #unit: s self.scan_axis_speed_readout = np.round( scan_axis_step_mm / actual_exposure_s / self.n_active_channels_readout, 5) #unit: mm/s self.scan_axis_speed_nuclei = np.round( scan_axis_step_mm / actual_exposure_s / self.n_active_channels_nuclei, 5) #unit: mm/s self.scan_axis_positions = np.rint( self.scan_axis_range_mm / scan_axis_step_mm).astype( int) #unit: number of positions # tile axis setup tile_axis_overlap = 0.2 #unit: percentage tile_axis_range_um = np.abs(self.tile_axis_end_um - self.tile_axis_start_um) #unit: um tile_axis_ROI = self.x_pixels * self.pixel_size_um #unit: um self.tile_axis_step_um = np.round( (tile_axis_ROI) * (1 - tile_axis_overlap), 2) #unit: um self.n_xy_tiles = np.rint( tile_axis_range_um / self.tile_axis_step_um).astype( int) + 1 #unit: number of positions # if tile_axis_positions rounded to zero, make sure we acquire at least one position if self.n_xy_tiles == 0: self.n_xy_tiles = 1 # height axis setup # check if there are multiple heights height_axis_range_um = np.abs( self.height_axis_end_um - self.height_axis_start_um) #unit: um # if multiple heights, check if heights are due to uneven tissue position or for z tiling height_axis_overlap = 0.2 #unit: percentage height_axis_ROI = self.y_pixels * self.pixel_size_um * np.sin( 30. * np.pi / 180.) #unit: um self.height_axis_step_um = np.round( (height_axis_ROI) * (1 - height_axis_overlap), 2) #unit: um self.n_z_tiles = np.rint( height_axis_range_um / self.height_axis_step_um).astype( int) + 1 #unit: number of positions # if height_axis_positions rounded to zero, make sure we acquire at least one position if self.n_z_tiles == 0: self.n_z_tiles = 1 # create dictionary with scan settings self.scan_settings = [{ 'exposure_ms': float(self.exposure_ms), 'scan_axis_start_um': float(self.scan_axis_start_um), 'scan_axis_end_um': float(self.scan_axis_end_um), 'scan_axis_step_um': float(self.scan_axis_step_um), 'tile_axis_start_um': float(self.tile_axis_start_um), 'tile_axis_end_um': float(self.tile_axis_end_um), 'tile_axis_step_um': float(self.tile_axis_step_um), 'height_axis_start_um': float(self.height_axis_start_um), 'height_axis_end_um': float(self.height_axis_end_um), 'height_axis_step_um': float(self.height_axis_step_um), 'n_iterative_rounds': int(self.n_iterative_rounds), 'nuclei_round': int(self.codebook['nuclei_round']), 'num_xy_tiles': int(self.n_xy_tiles), 'num_z_tiles': int(self.n_z_tiles), 'n_active_channels_readout': int(self.n_active_channels_readout), 'n_active_channels_nuclei': int(self.n_active_channels_nuclei), 'scan_axis_positions': int(self.scan_axis_positions), 'scan_axis_speed_readout': float(self.scan_axis_speed_readout), 'scan_axis_speed_nuclei': float(self.scan_axis_speed_nuclei), 'y_pixels': int(self.y_pixels), 'x_pixels': int(self.x_pixels), '405_active_readout': bool(self.channel_states_readout[0]), '488_active_readout': bool(self.channel_states_readout[1]), '561_active_readout': bool(self.channel_states_readout[2]), '635_active_readout': bool(self.channel_states_readout[3]), '730_active_readout': bool(self.channel_states_readout[4]), '405_power_readout': float(self.channel_powers_readout[0]), '488_power_readout': float(self.channel_powers_readout[1]), '561_power_readout': float(self.channel_powers_readout[2]), '635_power_readout': float(self.channel_powers_readout[3]), '730_power_readout': float(self.channel_powers_readout[4]), '405_active_nuclei': bool(self.channel_states_nuclei[0]), '488_active_nuclei': bool(self.channel_states_nuclei[1]), '561_active_nuclei': bool(self.channel_states_nuclei[2]), '635_active_nuclei': bool(self.channel_states_nuclei[3]), '730_active_nuclei': bool(self.channel_states_nuclei[4]), '405_power_nuclei': float(self.channel_powers_nuclei[0]), '488_power_nuclei': float(self.channel_powers_nuclei[1]), '561_power_nuclei': float(self.channel_powers_nuclei[2]), '635_power_nuclei': float(self.channel_powers_nuclei[3]), '730_power_nuclei': float(self.channel_powers_nuclei[4]) }] self.stage_volume_set = True except: raise Exception("Error in stage volume setup.")
def __init__(self, viewer: napari.viewer.Viewer, remote=True): super().__init__() self.setup_ui() self.viewer = viewer self.streaming_timer = None # create connection to mmcore server or process-local variant self._mmc = RemoteMMCore() if remote else CMMCorePlus() # tab widgets self.mda = MultiDWidget(self._mmc) self.explorer = ExploreSample(self.viewer, self._mmc) self.tabWidget.addTab(self.mda, "Multi-D Acquisition") self.tabWidget.addTab(self.explorer, "Sample Explorer") # connect mmcore signals sig = self._mmc.events # note: don't use lambdas with closures on `self`, since the connection # to core may outlive the lifetime of this particular widget. sig.sequenceStarted.connect(self._on_mda_started) sig.sequenceFinished.connect(self._on_mda_finished) sig.systemConfigurationLoaded.connect(self._refresh_options) sig.XYStagePositionChanged.connect(self._on_xy_stage_position_changed) sig.stagePositionChanged.connect(self._on_stage_position_changed) sig.exposureChanged.connect(self._on_exp_change) sig.frameReady.connect(self._on_mda_frame) sig.channelGroupChanged.connect(self._refresh_channel_list) sig.configSet.connect(self._on_config_set) # connect buttons self.load_cfg_Button.clicked.connect(self.load_cfg) self.browse_cfg_Button.clicked.connect(self.browse_cfg) self.left_Button.clicked.connect(self.stage_x_left) self.right_Button.clicked.connect(self.stage_x_right) self.y_up_Button.clicked.connect(self.stage_y_up) self.y_down_Button.clicked.connect(self.stage_y_down) self.up_Button.clicked.connect(self.stage_z_up) self.down_Button.clicked.connect(self.stage_z_down) self.snap_Button.clicked.connect(self.snap) self.live_Button.clicked.connect(self.toggle_live) self.illumination_Button.clicked.connect(self.illumination) self.properties_Button.clicked.connect(self._show_prop_browser) # connect comboBox self.objective_comboBox.currentIndexChanged.connect(self.change_objective) self.bit_comboBox.currentIndexChanged.connect(self.bit_changed) self.bin_comboBox.currentIndexChanged.connect(self.bin_changed) self.snap_channel_comboBox.currentTextChanged.connect(self._channel_changed) # connect spinboxes self.exp_spinBox.valueChanged.connect(self._update_exp) self.exp_spinBox.setKeyboardTracking(False) # refresh options in case a config is already loaded by another remote self._refresh_options() self.viewer.layers.events.connect(self.update_max_min) self.viewer.layers.selection.events.active.connect(self.update_max_min) self.viewer.dims.events.current_step.connect(self.update_max_min)
class MainWindow(QtW.QWidget, _MainUI): def __init__(self, viewer: napari.viewer.Viewer, remote=True): super().__init__() self.setup_ui() self.viewer = viewer self.streaming_timer = None # create connection to mmcore server or process-local variant self._mmc = RemoteMMCore() if remote else CMMCorePlus() # tab widgets self.mda = MultiDWidget(self._mmc) self.explorer = ExploreSample(self.viewer, self._mmc) self.tabWidget.addTab(self.mda, "Multi-D Acquisition") self.tabWidget.addTab(self.explorer, "Sample Explorer") # connect mmcore signals sig = self._mmc.events # note: don't use lambdas with closures on `self`, since the connection # to core may outlive the lifetime of this particular widget. sig.sequenceStarted.connect(self._on_mda_started) sig.sequenceFinished.connect(self._on_mda_finished) sig.systemConfigurationLoaded.connect(self._refresh_options) sig.XYStagePositionChanged.connect(self._on_xy_stage_position_changed) sig.stagePositionChanged.connect(self._on_stage_position_changed) sig.exposureChanged.connect(self._on_exp_change) sig.frameReady.connect(self._on_mda_frame) sig.channelGroupChanged.connect(self._refresh_channel_list) sig.configSet.connect(self._on_config_set) # connect buttons self.load_cfg_Button.clicked.connect(self.load_cfg) self.browse_cfg_Button.clicked.connect(self.browse_cfg) self.left_Button.clicked.connect(self.stage_x_left) self.right_Button.clicked.connect(self.stage_x_right) self.y_up_Button.clicked.connect(self.stage_y_up) self.y_down_Button.clicked.connect(self.stage_y_down) self.up_Button.clicked.connect(self.stage_z_up) self.down_Button.clicked.connect(self.stage_z_down) self.snap_Button.clicked.connect(self.snap) self.live_Button.clicked.connect(self.toggle_live) self.illumination_Button.clicked.connect(self.illumination) self.properties_Button.clicked.connect(self._show_prop_browser) # connect comboBox self.objective_comboBox.currentIndexChanged.connect(self.change_objective) self.bit_comboBox.currentIndexChanged.connect(self.bit_changed) self.bin_comboBox.currentIndexChanged.connect(self.bin_changed) self.snap_channel_comboBox.currentTextChanged.connect(self._channel_changed) # connect spinboxes self.exp_spinBox.valueChanged.connect(self._update_exp) self.exp_spinBox.setKeyboardTracking(False) # refresh options in case a config is already loaded by another remote self._refresh_options() self.viewer.layers.events.connect(self.update_max_min) self.viewer.layers.selection.events.active.connect(self.update_max_min) self.viewer.dims.events.current_step.connect(self.update_max_min) def illumination(self): if not hasattr(self, "_illumination"): self._illumination = IlluminationDialog(self._mmc, self) self._illumination.show() def _show_prop_browser(self): pb = PropBrowser(self._mmc, self) pb.exec() def _on_config_set(self, groupName: str, configName: str): if groupName == self._mmc.getOrGuessChannelGroup(): with blockSignals(self.snap_channel_comboBox): self.snap_channel_comboBox.setCurrentText(configName) def _set_enabled(self, enabled): self.objective_groupBox.setEnabled(enabled) self.camera_groupBox.setEnabled(enabled) self.XY_groupBox.setEnabled(enabled) self.Z_groupBox.setEnabled(enabled) self.snap_live_tab.setEnabled(enabled) self.snap_live_tab.setEnabled(enabled) def _update_exp(self, exposure: float): self._mmc.setExposure(exposure) if self.streaming_timer: self.streaming_timer.setInterval(int(exposure)) self._mmc.stopSequenceAcquisition() self._mmc.startContinuousSequenceAcquisition(exposure) def _on_exp_change(self, camera: str, exposure: float): with blockSignals(self.exp_spinBox): self.exp_spinBox.setValue(exposure) if self.streaming_timer: self.streaming_timer.setInterval(int(exposure)) def _on_mda_started(self, sequence: useq.MDASequence): """ "create temp folder and block gui when mda starts.""" self._set_enabled(False) def _on_mda_frame(self, image: np.ndarray, event: useq.MDAEvent): meta = self.mda.SEQUENCE_META.get(event.sequence) or SequenceMeta() if meta.mode != "mda": return # pick layer name file_name = meta.file_name if meta.should_save else "Exp" channelstr = ( f"[{event.channel.config}_idx{event.index['c']}]_" if meta.split_channels else "" ) layer_name = f"{file_name}_{channelstr}{event.sequence.uid}" try: # see if we already have a layer with this sequence layer = self.viewer.layers[layer_name] # get indices of new image im_idx = tuple( event.index[k] for k in event_indices(event) if not (meta.split_channels and k == "c") ) # make sure array shape contains im_idx, or pad with zeros new_array = extend_array_for_index(layer.data, im_idx) # add the incoming index at the appropriate index new_array[im_idx] = image # set layer data layer.data = new_array for a, v in enumerate(im_idx): self.viewer.dims.set_point(a, v) except KeyError: # add the new layer to the viewer seq = event.sequence _image = image[(np.newaxis,) * len(seq.shape)] layer = self.viewer.add_image(_image, name=layer_name, blending="additive") # dimensions labels labels = [i for i in seq.axis_order if i in event.index] + ["y", "x"] self.viewer.dims.axis_labels = labels # add metadata to layer layer.metadata["useq_sequence"] = seq layer.metadata["uid"] = seq.uid # storing event.index in addition to channel.config because it's # possible to have two of the same channel in one sequence. layer.metadata["ch_id"] = f'{event.channel.config}_idx{event.index["c"]}' def _on_mda_finished(self, sequence: useq.MDASequence): """Save layer and add increment to save name.""" meta = self.mda.SEQUENCE_META.pop(sequence, SequenceMeta()) save_sequence(sequence, self.viewer.layers, meta) # reactivate gui when mda finishes. self._set_enabled(True) def browse_cfg(self): self._mmc.unloadAllDevices() # unload all devicies print(f"Loaded Devices: {self._mmc.getLoadedDevices()}") # clear spinbox/combobox without accidently setting properties boxes = [ self.objective_comboBox, self.bin_comboBox, self.bit_comboBox, self.snap_channel_comboBox, ] with blockSignals(boxes): for box in boxes: box.clear() file_dir = QtW.QFileDialog.getOpenFileName(self, "", "", "cfg(*.cfg)") self.cfg_LineEdit.setText(str(file_dir[0])) self.max_min_val_label.setText("None") self.load_cfg_Button.setEnabled(True) def load_cfg(self): self.load_cfg_Button.setEnabled(False) print("loading", self.cfg_LineEdit.text()) self._mmc.loadSystemConfiguration(self.cfg_LineEdit.text()) def _refresh_camera_options(self): cam_device = self._mmc.getCameraDevice() if not cam_device: return cam_props = self._mmc.getDevicePropertyNames(cam_device) if "Binning" in cam_props: bin_opts = self._mmc.getAllowedPropertyValues(cam_device, "Binning") with blockSignals(self.bin_comboBox): self.bin_comboBox.clear() self.bin_comboBox.addItems(bin_opts) self.bin_comboBox.setCurrentText( self._mmc.getProperty(cam_device, "Binning") ) if "PixelType" in cam_props: px_t = self._mmc.getAllowedPropertyValues(cam_device, "PixelType") with blockSignals(self.bit_comboBox): self.bit_comboBox.clear() self.bit_comboBox.addItems(px_t) self.bit_comboBox.setCurrentText( self._mmc.getProperty(cam_device, "PixelType") ) def _refresh_objective_options(self): if "Objective" in self._mmc.getLoadedDevices(): with blockSignals(self.objective_comboBox): self.objective_comboBox.clear() self.objective_comboBox.addItems(self._mmc.getStateLabels("Objective")) self.objective_comboBox.setCurrentText( self._mmc.getStateLabel("Objective") ) def _refresh_channel_list(self, channel_group: str = None): if channel_group is None: channel_group = self._mmc.getOrGuessChannelGroup() if channel_group: channel_list = list(self._mmc.getAvailableConfigs(channel_group)) with blockSignals(self.snap_channel_comboBox): self.snap_channel_comboBox.clear() self.snap_channel_comboBox.addItems(channel_list) self.snap_channel_comboBox.setCurrentText( self._mmc.getCurrentConfig(channel_group) ) def _refresh_positions(self): if self._mmc.getXYStageDevice(): x, y = self._mmc.getXPosition(), self._mmc.getYPosition() self._on_xy_stage_position_changed(self._mmc.getXYStageDevice(), x, y) if self._mmc.getFocusDevice(): self.z_lineEdit.setText(f"{self._mmc.getZPosition():.1f}") def _refresh_options(self): self._refresh_camera_options() self._refresh_objective_options() self._refresh_channel_list() self._refresh_positions() def bit_changed(self): if self.bit_comboBox.count() > 0: bits = self.bit_comboBox.currentText() self._mmc.setProperty(self._mmc.getCameraDevice(), "PixelType", bits) def bin_changed(self): if self.bin_comboBox.count() > 0: bins = self.bin_comboBox.currentText() cd = self._mmc.getCameraDevice() self._mmc.setProperty(cd, "Binning", bins) def _channel_changed(self, newChannel: str): channel_group = self._mmc.getOrGuessChannelGroup() if channel_group: self._mmc.setConfig(channel_group, newChannel) def _on_xy_stage_position_changed(self, name, x, y): self.x_lineEdit.setText(f"{x:.1f}") self.y_lineEdit.setText(f"{y:.1f}") def _on_stage_position_changed(self, name, value): if "z" in name.lower(): # hack self.z_lineEdit.setText(f"{value:.1f}") def stage_x_left(self): self._mmc.setRelativeXYPosition(-float(self.xy_step_size_SpinBox.value()), 0.0) if self.snap_on_click_xy_checkBox.isChecked(): self.snap() def stage_x_right(self): self._mmc.setRelativeXYPosition(float(self.xy_step_size_SpinBox.value()), 0.0) if self.snap_on_click_xy_checkBox.isChecked(): self.snap() def stage_y_up(self): self._mmc.setRelativeXYPosition( 0.0, float(self.xy_step_size_SpinBox.value()), ) if self.snap_on_click_xy_checkBox.isChecked(): self.snap() def stage_y_down(self): self._mmc.setRelativeXYPosition( 0.0, -float(self.xy_step_size_SpinBox.value()), ) if self.snap_on_click_xy_checkBox.isChecked(): self.snap() def stage_z_up(self): self._mmc.setRelativeXYZPosition( 0.0, 0.0, float(self.z_step_size_doubleSpinBox.value()) ) if self.snap_on_click_z_checkBox.isChecked(): self.snap() def stage_z_down(self): self._mmc.setRelativeXYZPosition( 0.0, 0.0, -float(self.z_step_size_doubleSpinBox.value()) ) if self.snap_on_click_z_checkBox.isChecked(): self.snap() def change_objective(self): if self.objective_comboBox.count() <= 0: return zdev = self._mmc.getFocusDevice() currentZ = self._mmc.getZPosition() self._mmc.setPosition(zdev, 0) self._mmc.waitForDevice(zdev) self._mmc.setProperty( "Objective", "Label", self.objective_comboBox.currentText() ) self._mmc.waitForDevice("Objective") self._mmc.setPosition(zdev, currentZ) self._mmc.waitForDevice(zdev) # define and set pixel size Config self._mmc.deletePixelSizeConfig(self._mmc.getCurrentPixelSizeConfig()) curr_obj_name = self._mmc.getProperty("Objective", "Label") self._mmc.definePixelSizeConfig(curr_obj_name) self._mmc.setPixelSizeConfig(curr_obj_name) # get magnification info from the objective name # and set image pixel sixe (x,y) for the current pixel size Config match = re.search(r"(\d{1,3})[xX]", curr_obj_name) if match: mag = int(match.groups()[0]) self.image_pixel_size = self.px_size_doubleSpinBox.value() / mag self._mmc.setPixelSizeUm( self._mmc.getCurrentPixelSizeConfig(), self.image_pixel_size ) def update_viewer(self, data=None): # TODO: - fix the fact that when you change the objective # the image translation is wrong # - are max and min_val_lineEdit updating in live mode? if data is None: try: data = self._mmc.getLastImage() except (RuntimeError, IndexError): # circular buffer empty return try: preview_layer = self.viewer.layers["preview"] preview_layer.data = data except KeyError: preview_layer = self.viewer.add_image(data, name="preview") self.update_max_min() if self.streaming_timer is None: self.viewer.reset_view() def update_max_min(self, event=None): if self.tabWidget.currentIndex() != 0: return min_max_txt = "" for layer in self.viewer.layers.selection: if isinstance(layer, napari.layers.Image) and layer.visible: col = layer.colormap.name if col not in QColor.colorNames(): col = "gray" # min and max of current slice min_max_show = tuple(layer._calc_data_range(mode="slice")) min_max_txt += f'<font color="{col}">{min_max_show}</font>' self.max_min_val_label.setText(min_max_txt) def snap(self): self.stop_live() self._mmc.snapImage() self.update_viewer(self._mmc.getImage()) def start_live(self): self._mmc.startContinuousSequenceAcquisition(self.exp_spinBox.value()) self.streaming_timer = QTimer() self.streaming_timer.timeout.connect(self.update_viewer) self.streaming_timer.start(int(self.exp_spinBox.value())) self.live_Button.setText("Stop") def stop_live(self): self._mmc.stopSequenceAcquisition() if self.streaming_timer is not None: self.streaming_timer.stop() self.streaming_timer = None self.live_Button.setText("Live") self.live_Button.setIcon(CAM_ICON) def toggle_live(self, event=None): if self.streaming_timer is None: ch_group = self._mmc.getOrGuessChannelGroup() self._mmc.setConfig(ch_group, self.snap_channel_comboBox.currentText()) self.start_live() self.live_Button.setIcon(CAM_STOP_ICON) else: self.stop_live() self.live_Button.setIcon(CAM_ICON)
def _acquire_3d_data(self): while True: with RemoteMMCore() as mmc_3d: #------------------------------------------------------------------------------------------------------------------------------------ #----------------------------------------------Begin setup of scan parameters-------------------------------------------------------- #------------------------------------------------------------------------------------------------------------------------------------ # parse which channels are active active_channel_indices = [ind for ind, st in zip(self.do_ind, self.channel_states) if st] n_active_channels = len(active_channel_indices) if self.debug: print("%d active channels: " % n_active_channels, end="") for ind in active_channel_indices: print("%s " % self.channel_labels[ind], end="") print("") n_timepoints = 1 if self.ROI_changed: self._crop_camera() self.ROI_changed = False # set exposure time if self.exposure_changed: mmc_3d.setExposure(self.exposure_ms) self.exposure_changed = False if self.powers_changed: self._set_mmc_laser_power() self.powers_changed = False if self.channels_changed or self.footprint_changed or not(self.DAQ_running): if self.DAQ_running: self.opmdaq.stop_waveform_playback() self.DAQ_running = False self.opmdaq.set_scan_type('mirror') self.opmdaq.set_channels_to_use(self.channel_states) self.opmdaq.set_interleave_mode(True) scan_steps = self.opmdaq.set_scan_mirror_range(self.scan_axis_step_um,self.scan_mirror_footprint_um) self.opmdaq.generate_waveforms() self.channels_changed = False self.footprint_changed = False raw_image_stack = np.zeros([self.do_ind[-1],scan_steps,self.ROI_width_y,self.ROI_width_x]).astype(np.uint16) if self.debug: # output experiment info print("Scan axis range: %.1f um, Scan axis step: %.1f nm, Number of galvo positions: %d" % (self.scan_mirror_footprint_um, self.scan_axis_step_um * 1000, scan_steps)) print('Time points: ' + str(n_timepoints)) #------------------------------------------------------------------------------------------------------------------------------------ #----------------------------------------------End setup of scan parameters---------------------------------------------------------- #------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------ #----------------------------------------------Start acquisition and deskew---------------------------------------------------------- #------------------------------------------------------------------------------------------------------------------------------------ self.opmdaq.start_waveform_playback() self.DAQ_running = True # run hardware triggered acquisition mmc_3d.startSequenceAcquisition(int(n_active_channels*scan_steps),0,True) for z in range(scan_steps): for c in active_channel_indices: while mmc_3d.getRemainingImageCount()==0: pass raw_image_stack[c,z,:] = mmc_3d.popNextImage() mmc_3d.stopSequenceAcquisition() self.opmdaq.stop_waveform_playback() self.DAQ_running = False # deskew parameters deskew_parameters = np.empty([3]) deskew_parameters[0] = self.opm_tilt # (degrees) deskew_parameters[1] = self.scan_axis_step_um*100 # (nm) deskew_parameters[2] = self.camera_pixel_size_um*100 # (nm) for c in active_channel_indices: deskewed_image = deskew(np.flipud(raw_image_stack[c,:]),*deskew_parameters).astype(np.uint16) yield c, deskewed_image del raw_image_stack