class CameraGUI(GUIBase): """ Main spectrometer camera class. """ camera_logic = Connector(interface='CameraLogic') savelogic = Connector(interface='SaveLogic') sigVideoStart = QtCore.Signal() sigVideoStop = QtCore.Signal() sigImageStart = QtCore.Signal() sigImageStop = QtCore.Signal() _image = [] _logic = None _mw = None def __init__(self, config, **kwargs): # load connection super().__init__(config=config, **kwargs) def on_activate(self): """ Initializes all needed UI files and establishes the connectors. """ self._logic = self.camera_logic() self._save_logic = self.savelogic() # Windows self._mw = CameraWindow() self._mw.centralwidget.hide() self._mw.setDockNestingEnabled(True) self.initSettingsUI() self._mw.start_video_Action.setEnabled(True) self._mw.start_video_Action.setChecked(self._logic.enabled) self._mw.start_video_Action.triggered.connect(self.start_video_clicked) self._mw.start_image_Action.setEnabled(True) self._mw.start_image_Action.setChecked(self._logic.enabled) self._mw.start_image_Action.triggered.connect(self.start_image_clicked) self._logic.sigUpdateDisplay.connect(self.update_data) self._logic.sigAcquisitionFinished.connect(self.acquisition_finished) self._logic.sigVideoFinished.connect(self.enable_start_image_action) # starting the physical measurement self.sigVideoStart.connect(self._logic.start_loop) self.sigVideoStop.connect(self._logic.stop_loop) self.sigImageStart.connect(self._logic.start_single_acquistion) # connect Settings action under Options menu self._mw.actionSettings.triggered.connect(self.menu_settings) # connect save action to save function self._mw.actionSave_XY_Scan.triggered.connect(self.save_xy_scan_data) raw_data_image = self._logic.get_last_image() self._image = pg.ImageItem(image=raw_data_image, axisOrder='row-major') self._mw.image_PlotWidget.addItem(self._image) self._mw.image_PlotWidget.setAspectLocked(True) # Get the colorscale and set the LUTs self.my_colors = ColorScaleInferno() self._image.setLookupTable(self.my_colors.lut) # Connect the buttons and inputs for the colorbar self._mw.xy_cb_manual_RadioButton.clicked.connect(self.update_xy_cb_range) self._mw.xy_cb_centiles_RadioButton.clicked.connect(self.update_xy_cb_range) self._mw.xy_cb_min_DoubleSpinBox.valueChanged.connect(self.shortcut_to_xy_cb_manual) self._mw.xy_cb_max_DoubleSpinBox.valueChanged.connect(self.shortcut_to_xy_cb_manual) self._mw.xy_cb_low_percentile_DoubleSpinBox.valueChanged.connect(self.shortcut_to_xy_cb_centiles) self._mw.xy_cb_high_percentile_DoubleSpinBox.valueChanged.connect(self.shortcut_to_xy_cb_centiles) # create color bar self.xy_cb = ColorBar(self.my_colors.cmap_normed, width=100, cb_min=0, cb_max=100) self.depth_cb = ColorBar(self.my_colors.cmap_normed, width=100, cb_min=0, cb_max=100) self._mw.xy_cb_ViewWidget.addItem(self.xy_cb) self._mw.xy_cb_ViewWidget.hideAxis('bottom') self._mw.xy_cb_ViewWidget.setLabel('left', 'Fluorescence', units='c') self._mw.xy_cb_ViewWidget.setMouseEnabled(x=False, y=False) def on_deactivate(self): """ Deinitialisation performed during deactivation of the module. """ self._mw.close() def show(self): """Make window visible and put it above all other windows. """ QtWidgets.QMainWindow.show(self._mw) self._mw.activateWindow() self._mw.raise_() def initSettingsUI(self): """ Definition, configuration and initialisation of the settings GUI. This init connects all the graphic modules, which were created in the *.ui file and configures the event handling between the modules. Moreover it sets default values if not existed in the logic modules. """ # Create the Settings window self._sd = CameraSettingDialog() # Connect the action of the settings window with the code: self._sd.accepted.connect(self.update_settings) self._sd.rejected.connect(self.keep_former_settings) self._sd.buttonBox.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect(self.update_settings) # write the configuration to the settings window of the GUI. self.keep_former_settings() def update_settings(self): """ Write new settings from the gui to the file. """ self._logic.set_exposure(self._sd.exposureDSpinBox.value()) self._logic.set_gain(self._sd.gainSpinBox.value()) def keep_former_settings(self): """ Keep the old settings and restores them in the gui. """ self._sd.exposureDSpinBox.setValue(self._logic._exposure) self._sd.gainSpinBox.setValue(self._logic._gain) def menu_settings(self): """ This method opens the settings menu. """ self._sd.exec_() def start_image_clicked(self): self.sigImageStart.emit() self._mw.start_image_Action.setDisabled(True) self._mw.start_video_Action.setDisabled(True) def acquisition_finished(self): self._mw.start_image_Action.setChecked(False) self._mw.start_image_Action.setDisabled(False) self._mw.start_video_Action.setDisabled(False) def start_video_clicked(self): """ Handling the Start button to stop and restart the counter. """ self._mw.start_image_Action.setDisabled(True) if self._logic.enabled: self._mw.start_video_Action.setText('Start Video') self.sigVideoStop.emit() else: self._mw.start_video_Action.setText('Stop Video') self.sigVideoStart.emit() def enable_start_image_action(self): self._mw.start_image_Action.setEnabled(True) def update_data(self): """ Get the image data from the logic and print it on the window """ raw_data_image = self._logic.get_last_image() levels = (0., 1.) self._image.setImage(image=raw_data_image) self.update_xy_cb_range() # self._image.setImage(image=raw_data_image, levels=levels) def updateView(self): """ Update the view when the model change """ pass # color bar functions def get_xy_cb_range(self): """ Determines the cb_min and cb_max values for the xy scan image """ # If "Manual" is checked, or the image data is empty (all zeros), then take manual cb range. if self._mw.xy_cb_manual_RadioButton.isChecked() or np.max(self._image.image) == 0.0: cb_min = self._mw.xy_cb_min_DoubleSpinBox.value() cb_max = self._mw.xy_cb_max_DoubleSpinBox.value() # Otherwise, calculate cb range from percentiles. else: # xy_image_nonzero = self._image.image[np.nonzero(self._image.image)] # Read centile range low_centile = self._mw.xy_cb_low_percentile_DoubleSpinBox.value() high_centile = self._mw.xy_cb_high_percentile_DoubleSpinBox.value() cb_min = np.percentile(self._image.image, low_centile) cb_max = np.percentile(self._image.image, high_centile) cb_range = [cb_min, cb_max] return cb_range def refresh_xy_colorbar(self): """ Adjust the xy colorbar. Calls the refresh method from colorbar, which takes either the lowest and higherst value in the image or predefined ranges. Note that you can invert the colorbar if the lower border is bigger then the higher one. """ cb_range = self.get_xy_cb_range() self.xy_cb.refresh_colorbar(cb_range[0], cb_range[1]) def refresh_xy_image(self): """ Update the current XY image from the logic. Everytime the scanner is scanning a line in xy the image is rebuild and updated in the GUI. """ self._image.getViewBox().updateAutoRange() xy_image_data = self._logic._last_image cb_range = self.get_xy_cb_range() # Now update image with new color scale, and update colorbar self._image.setImage(image=xy_image_data, levels=(cb_range[0], cb_range[1])) self.refresh_xy_colorbar() def shortcut_to_xy_cb_manual(self): """Someone edited the absolute counts range for the xy colour bar, better update.""" self._mw.xy_cb_manual_RadioButton.setChecked(True) self.update_xy_cb_range() def shortcut_to_xy_cb_centiles(self): """Someone edited the centiles range for the xy colour bar, better update.""" self._mw.xy_cb_centiles_RadioButton.setChecked(True) self.update_xy_cb_range() def update_xy_cb_range(self): """Redraw xy colour bar and scan image.""" self.refresh_xy_colorbar() self.refresh_xy_image() # save functions def save_xy_scan_data(self): """ Run the save routine from the logic to save the xy confocal data.""" cb_range = self.get_xy_cb_range() # Percentile range is None, unless the percentile scaling is selected in GUI. pcile_range = None if not self._mw.xy_cb_manual_RadioButton.isChecked(): low_centile = self._mw.xy_cb_low_percentile_DoubleSpinBox.value() high_centile = self._mw.xy_cb_high_percentile_DoubleSpinBox.value() pcile_range = [low_centile, high_centile] self._logic.save_xy_data(colorscale_range=cb_range, percentile_range=pcile_range) # TODO: find a way to produce raw image in savelogic. For now it is saved here. filepath = self._save_logic.get_path_for_module(module_name='Confocal') filename = filepath + os.sep + time.strftime('%Y%m%d-%H%M-%S_confocal_xy_scan_raw_pixel_image') self._image.save(filename + '_raw.png')
class PoiManagerGui(GUIBase): """ This is the GUI Class for PoiManager """ _modclass = 'PoiManagerGui' _modtype = 'gui' # declare connectors poimanagerlogic1 = Connector(interface='PoiManagerLogic') confocallogic1 = Connector(interface='ConfocalLogic') def __init__(self, config, **kwargs): super().__init__(config=config, **kwargs) def on_activate(self): """ Initializes the overall GUI, and establishes the connectors. This method executes the init methods for each of the GUIs. """ # Connectors self._poi_manager_logic = self.poimanagerlogic1() self._confocal_logic = self.confocallogic1() self.log.debug("POI Manager logic is {0}".format( self._poi_manager_logic)) self.log.debug("Confocal logic is {0}".format(self._confocal_logic)) # Initializing the GUIs self.initMainUI() self.initReorientRoiDialogUI() # There could be POIs created in the logic already, so update lists and map self.populate_poi_list() self._redraw_sample_shift() self._redraw_poi_markers() def mouseMoved(self, event): """ Handles any mouse movements inside the image. @param event: Event that signals the new mouse movement. This should be of type QPointF. Gets the mouse position, converts it to a position scaled to the image axis and than calculates and updated the position to the current POI. """ # converts the absolute mouse position to a position relative to the axis mouse_point = self._mw.roi_map_ViewWidget.getPlotItem().getViewBox( ).mapSceneToView(event.toPoint()) #self.log.debug("Mouse at x = {0:0.2e}, y = {1:0.2e}".format(mouse_point.x(), mouse_point.y())) # only calculate distance, if a POI is selected if self._poi_manager_logic.active_poi is not None: cur_poi_pos = self._poi_manager_logic.get_poi_position( poikey=self._poi_manager_logic.active_poi.get_key()) dx = ScaledFloat(mouse_point.x() - cur_poi_pos[0]) dy = ScaledFloat(mouse_point.y() - cur_poi_pos[1]) d_total = ScaledFloat( np.sqrt((mouse_point.x() - cur_poi_pos[0])**2 + (mouse_point.y() - cur_poi_pos[1])**2)) self._mw.poi_distance_label.setText( '{0:.2r}m ({1:.2r}m, {2:.2r}m)'.format(d_total, dx, dy)) def initMainUI(self): """ Definition, configuration and initialisation of the POI Manager GUI. This init connects all the graphic modules, which were created in the *.ui file and configures the event handling between the modules. """ # Use the inherited class 'Ui_PoiManagerGuiTemplate' to create now the # GUI element: self._mw = PoiManagerMainWindow() ##################### # Configuring the dock widgets ##################### # All our gui elements are dockable, and so there should be no "central" widget. self._mw.centralwidget.hide() self._mw.setDockNestingEnabled(True) ##################### # Setting up display of ROI map xy image ##################### # Get the image for the display from the logic: self.roi_xy_image_data = self._poi_manager_logic.roi_map_data[:, :, 3] # Load the image in the display: self.roi_map_image = pg.ImageItem(image=self.roi_xy_image_data, axisOrder='row-major') self.roi_map_image.setRect( QtCore.QRectF( self._confocal_logic.image_x_range[0], self._confocal_logic.image_y_range[0], self._confocal_logic.image_x_range[1] - self._confocal_logic.image_x_range[0], self._confocal_logic.image_y_range[1] - self._confocal_logic.image_y_range[0])) # Add the display item to the roi map ViewWidget defined in the UI file self._mw.roi_map_ViewWidget.addItem(self.roi_map_image) self._mw.roi_map_ViewWidget.setLabel('bottom', 'X position', units='m') self._mw.roi_map_ViewWidget.setLabel('left', 'Y position', units='m') # Set to fixed 1.0 aspect ratio, since the metaphor is a "map" of the sample self._mw.roi_map_ViewWidget.setAspectLocked(lock=True, ratio=1.0) # Get the colorscales and set LUT my_colors = ColorScaleInferno() self.roi_map_image.setLookupTable(my_colors.lut) # Add color bar: self.roi_cb = ColorBar(my_colors.cmap_normed, 100, 0, 100000) self._mw.roi_cb_ViewWidget.addItem(self.roi_cb) self._mw.roi_cb_ViewWidget.hideAxis('bottom') self._mw.roi_cb_ViewWidget.setLabel('left', 'Fluorescence', units='c/s') self._mw.roi_cb_ViewWidget.setMouseEnabled(x=False, y=False) ##################### # Setting up display of sample shift plot ##################### # Load image in the display self.x_shift_plot = pg.PlotDataItem([0], [0], pen=pg.mkPen( palette.c1, style=QtCore.Qt.DotLine), symbol='o', symbolPen=palette.c1, symbolBrush=palette.c1, symbolSize=5, name='x') self.y_shift_plot = pg.PlotDataItem([0], [0], pen=pg.mkPen( palette.c2, style=QtCore.Qt.DotLine), symbol='s', symbolPen=palette.c2, symbolBrush=palette.c2, symbolSize=5, name='y') self.z_shift_plot = pg.PlotDataItem([0], [0], pen=pg.mkPen( palette.c3, style=QtCore.Qt.DotLine), symbol='t', symbolPen=palette.c3, symbolBrush=palette.c3, symbolSize=5, name='z') self._mw.sample_shift_ViewWidget.addLegend() # Add the plot to the ViewWidget defined in the UI file self._mw.sample_shift_ViewWidget.addItem(self.x_shift_plot) self._mw.sample_shift_ViewWidget.addItem(self.y_shift_plot) self._mw.sample_shift_ViewWidget.addItem(self.z_shift_plot) # Label axes self._mw.sample_shift_ViewWidget.setLabel('bottom', 'Time', units='s') self._mw.sample_shift_ViewWidget.setLabel('left', 'Sample shift', units='m') ##################### # Connect signals ##################### # Distance Measurement: # Introducing a SignalProxy will limit the rate of signals that get fired. # Otherwise we will run into a heap of unhandled function calls. proxy = pg.SignalProxy(self.roi_map_image.scene().sigMouseMoved, rateLimit=60, slot=self.mouseMoved) # Connecting a Mouse Signal to trace to mouse movement function. self.roi_map_image.scene().sigMouseMoved.connect(self.mouseMoved) # Toolbar actions self._mw.new_roi_Action.triggered.connect(self.make_new_roi) self._mw.save_roi_Action.triggered.connect(self.save_roi) self._mw.load_roi_Action.triggered.connect(self.load_roi) self._mw.reorient_roi_Action.triggered.connect( self.open_reorient_roi_dialog) self._mw.autofind_pois_Action.triggered.connect( self.do_autofind_poi_procedure) self._mw.optimize_roi_Action.triggered.connect(self.optimize_roi) self._mw.new_poi_Action.triggered.connect(self.set_new_poi) self._mw.goto_poi_Action.triggered.connect(self.goto_poi) self._mw.refind_poi_Action.triggered.connect(self.update_poi_pos) self._mw.track_poi_Action.triggered.connect(self.toggle_tracking) # Interface controls self._mw.get_confocal_image_PushButton.clicked.connect( self.get_confocal_image) self._mw.set_poi_PushButton.clicked.connect(self.set_new_poi) self._mw.delete_last_pos_Button.clicked.connect(self.delete_last_point) self._mw.manual_update_poi_PushButton.clicked.connect( self.manual_update_poi) self._mw.move_poi_PushButton.clicked.connect(self.move_poi) self._mw.poi_name_LineEdit.returnPressed.connect(self.change_poi_name) self._mw.roi_name_LineEdit.editingFinished.connect(self.set_roi_name) self._mw.delete_poi_PushButton.clicked.connect(self.delete_poi) self._mw.goto_poi_after_update_checkBox.toggled.connect( self.toggle_follow) # This needs to be activated so that it only listens to user input, and ignores # algorithmic index changes self._mw.active_poi_ComboBox.activated.connect( self.handle_active_poi_ComboBox_index_change) self._mw.refind_method_ComboBox.currentIndexChanged.connect( self.change_refind_method) # Connect the buttons and inputs for the colorbar self._mw.roi_cb_centiles_RadioButton.toggled.connect( self.refresh_roi_colorscale) self._mw.roi_cb_manual_RadioButton.toggled.connect( self.refresh_roi_colorscale) self._mw.roi_cb_min_SpinBox.valueChanged.connect( self.shortcut_to_roi_cb_manual) self._mw.roi_cb_max_SpinBox.valueChanged.connect( self.shortcut_to_roi_cb_manual) self._mw.roi_cb_low_percentile_DoubleSpinBox.valueChanged.connect( self.shortcut_to_roi_cb_centiles) self._mw.roi_cb_high_percentile_DoubleSpinBox.valueChanged.connect( self.shortcut_to_roi_cb_centiles) self._mw.display_shift_vs_duration_RadioButton.toggled.connect( self._redraw_sample_shift) self._mw.display_shift_vs_clocktime_RadioButton.toggled.connect( self._redraw_sample_shift) self._markers = dict() # Signal at end of refocus self._poi_manager_logic.signal_timer_updated.connect( self._update_timer, QtCore.Qt.QueuedConnection) self._poi_manager_logic.signal_poi_updated.connect( self._redraw_sample_shift, QtCore.Qt.QueuedConnection) self._poi_manager_logic.signal_poi_updated.connect( self.populate_poi_list, QtCore.Qt.QueuedConnection) self._poi_manager_logic.signal_poi_updated.connect( self._redraw_poi_markers, QtCore.Qt.QueuedConnection) self._poi_manager_logic.signal_poi_deleted.connect( self._remove_poi_marker) self._poi_manager_logic.signal_confocal_image_updated.connect( self._redraw_roi_image) self._poi_manager_logic.signal_periodic_opt_duration_changed.connect( self._track_period_changed) self._poi_manager_logic.signal_periodic_opt_started.connect( self._tracking_started) self._poi_manager_logic.signal_periodic_opt_stopped.connect( self._tracking_stopped) # Connect track period after setting the GUI value from the logic initial_period = self._poi_manager_logic.timer_duration self._mw.track_period_SpinBox.setValue(initial_period) self._mw.time_till_next_update_ProgressBar.setMaximum(initial_period) self._mw.time_till_next_update_ProgressBar.setValue(initial_period) self._mw.track_period_SpinBox.valueChanged.connect( self.set_track_period) # Redraw the sample_shift axes if the range changes self._mw.sample_shift_ViewWidget.plotItem.sigRangeChanged.connect( self._redraw_sample_shift) self._mw.show() def initReorientRoiDialogUI(self): """ Definition, configuration and initialization fo the Reorient ROI Dialog GUI. This init connects all the graphic modules which were created in the *.ui file and configures event handling. """ # Create the Reorient ROI Dialog window self._rrd = ReorientRoiDialog() # Connect the QDialog buttons to methods in the GUI self._rrd.accepted.connect(self.do_roi_reorientation) self._rrd.rejected.connect(self.reset_reorientation_dialog) # Connect the at_crosshair buttons self._rrd.ref_a_at_crosshair_PushButton.clicked.connect( self.ref_a_at_crosshair) self._rrd.ref_b_at_crosshair_PushButton.clicked.connect( self.ref_b_at_crosshair) self._rrd.ref_c_at_crosshair_PushButton.clicked.connect( self.ref_c_at_crosshair) # Connect input value changes to update the sanity-check values self._rrd.ref_a_poi_ComboBox.activated.connect( self.reorientation_sanity_check) self._rrd.ref_b_poi_ComboBox.activated.connect( self.reorientation_sanity_check) self._rrd.ref_c_poi_ComboBox.activated.connect( self.reorientation_sanity_check) self._rrd.ref_a_x_pos_DoubleSpinBox.valueChanged.connect( self.reorientation_sanity_check) self._rrd.ref_a_y_pos_DoubleSpinBox.valueChanged.connect( self.reorientation_sanity_check) self._rrd.ref_a_z_pos_DoubleSpinBox.valueChanged.connect( self.reorientation_sanity_check) self._rrd.ref_b_x_pos_DoubleSpinBox.valueChanged.connect( self.reorientation_sanity_check) self._rrd.ref_b_y_pos_DoubleSpinBox.valueChanged.connect( self.reorientation_sanity_check) self._rrd.ref_b_z_pos_DoubleSpinBox.valueChanged.connect( self.reorientation_sanity_check) self._rrd.ref_c_x_pos_DoubleSpinBox.valueChanged.connect( self.reorientation_sanity_check) self._rrd.ref_c_y_pos_DoubleSpinBox.valueChanged.connect( self.reorientation_sanity_check) self._rrd.ref_c_z_pos_DoubleSpinBox.valueChanged.connect( self.reorientation_sanity_check) def on_deactivate(self): """ Deinitialisation performed during deactivation of the module. """ self._mw.close() def show(self): """Make main window visible and put it above all other windows. """ QtWidgets.QMainWindow.show(self._mw) self._mw.activateWindow() self._mw.raise_() def get_confocal_image(self): """ Update the roi_map_data in poi manager logic, and use this updated data to redraw an image of the ROI. """ # Make poi manager logic get the confocal data self._poi_manager_logic.get_confocal_image_data() def _redraw_roi_image(self): # the image data is the fluorescence part self.roi_xy_image_data = self._poi_manager_logic.roi_map_data[:, :, 3] # Also get the x and y range limits and hold them locally self.roi_map_xmin = np.min(self._poi_manager_logic.roi_map_data[:, :, 0]) self.roi_map_xmax = np.max(self._poi_manager_logic.roi_map_data[:, :, 0]) self.roi_map_ymin = np.min(self._poi_manager_logic.roi_map_data[:, :, 1]) self.roi_map_ymax = np.max(self._poi_manager_logic.roi_map_data[:, :, 1]) self.roi_map_image.getViewBox().enableAutoRange() self.roi_map_image.setRect( QtCore.QRectF(self.roi_map_xmin, self.roi_map_ymin, self.roi_map_xmax - self.roi_map_xmin, self.roi_map_ymax - self.roi_map_ymin)) self.roi_map_image.setImage(image=self.roi_xy_image_data, autoLevels=True) def shortcut_to_roi_cb_manual(self): self._mw.roi_cb_manual_RadioButton.setChecked(True) self.refresh_roi_colorscale() def shortcut_to_roi_cb_centiles(self): self._mw.roi_cb_centiles_RadioButton.setChecked(True) self.refresh_roi_colorscale() def refresh_roi_colorscale(self): """ Adjust the colorbar in the ROI xy image, and update the image with the new color scale. Calls the refresh method from colorbar, which takes either the lowest and higherst value in the image or predefined ranges. Note that you can invert the colorbar if the lower border is bigger then the higher one. """ cb_min, cb_max = self.determine_cb_range() self.roi_map_image.setImage(image=self.roi_xy_image_data, levels=(cb_min, cb_max)) self.roi_cb.refresh_colorbar(cb_min, cb_max) self._mw.roi_cb_ViewWidget.update() def determine_cb_range(self): """ Process UI input to determine color bar range""" # If "Centiles" is checked, adjust colour scaling automatically to centiles. # Otherwise, take user-defined values. if self._mw.roi_cb_centiles_RadioButton.isChecked(): low_centile = self._mw.roi_cb_low_percentile_DoubleSpinBox.value() high_centile = self._mw.roi_cb_high_percentile_DoubleSpinBox.value( ) cb_min = np.percentile(self.roi_xy_image_data, low_centile) cb_max = np.percentile(self.roi_xy_image_data, high_centile) else: cb_min = self._mw.roi_cb_min_SpinBox.value() cb_max = self._mw.roi_cb_max_SpinBox.value() return cb_min, cb_max def set_new_poi(self): """ This method sets a new poi from the current crosshair position.""" key = self._poi_manager_logic.add_poi() def delete_last_point(self): """ Delete the last track position of a chosen poi. """ if self._poi_manager_logic.active_poi is None: self.log.warning("No POI selected. No datapoint can be deleted") else: self._poi_manager_logic.delete_last_position( poikey=self._poi_manager_logic.active_poi.get_key()) def delete_poi(self): """ Delete the active poi from the list of managed points. """ if self._poi_manager_logic.active_poi is None: self.log.warning("No POI selected.") else: key = self._poi_manager_logic.active_poi.get_key() # todo: this needs to handle the case where the logic deletes a POI. self._poi_manager_logic.delete_poi(poikey=key) def _remove_poi_marker(self, poikey): """ Remove the POI marker for a POI that was deleted. """ self._markers[poikey].delete_from_viewwidget() del self._markers[poikey] def manual_update_poi(self): """ Manually adds a point to the trace of a given poi without refocussing, and uses that information to update sample position. """ if self._poi_manager_logic.active_poi is None: self.log.warning("No POI selected.") else: self._poi_manager_logic.set_new_position( poikey=self._poi_manager_logic.active_poi.get_key()) def move_poi(self): """Manually move a POI to a new location in the sample map, but WITHOUT changing the sample position. This moves a POI relative to all the others. """ if self._poi_manager_logic.active_poi is None: self.log.warning("No POI selected.") else: self._poi_manager_logic.move_coords( poikey=self._poi_manager_logic.active_poi.get_key()) def toggle_tracking(self): if self._poi_manager_logic.active_poi is None: self.log.warning("No POI selected.") else: if self._poi_manager_logic.timer is None: self._poi_manager_logic.start_periodic_refocus( poikey=self._poi_manager_logic.active_poi.get_key()) else: self._poi_manager_logic.stop_periodic_refocus() def _tracking_started(self): self._mw.track_poi_Action.setChecked(True) def _tracking_stopped(self): self._mw.track_poi_Action.setChecked(False) def goto_poi(self, key): """ Go to the last known position of poi <key>.""" if self._poi_manager_logic.active_poi is None: self.log.warning("No POI selected.") else: self._poi_manager_logic.go_to_poi( poikey=self._poi_manager_logic.active_poi.get_key()) def populate_poi_list(self): """ Populate the dropdown box for selecting a poi. """ self.log.debug('started populate_poi_list at {0}'.format(time.time())) self._mw.active_poi_ComboBox.clear() self._mw.offset_anchor_ComboBox.clear() self._rrd.ref_a_poi_ComboBox.clear() self._rrd.ref_b_poi_ComboBox.clear() self._rrd.ref_c_poi_ComboBox.clear() for key in self._poi_manager_logic.get_all_pois(abc_sort=True): if key is not 'crosshair' and key is not 'sample': poi_list_empty = False self._mw.active_poi_ComboBox.addItem( self._poi_manager_logic.poi_list[key].get_name(), key) self._mw.offset_anchor_ComboBox.addItem( self._poi_manager_logic.poi_list[key].get_name(), key) self._rrd.ref_a_poi_ComboBox.addItem( self._poi_manager_logic.poi_list[key].get_name(), key) self._rrd.ref_b_poi_ComboBox.addItem( self._poi_manager_logic.poi_list[key].get_name(), key) self._rrd.ref_c_poi_ComboBox.addItem( self._poi_manager_logic.poi_list[key].get_name(), key) # If there is no active POI, set the combobox to nothing (-1) if self._poi_manager_logic.active_poi is None: self._mw.active_poi_ComboBox.setCurrentIndex(-1) # Otherwise, set it to the active POI else: self._mw.active_poi_ComboBox.setCurrentIndex( self._mw.active_poi_ComboBox.findData( self._poi_manager_logic.active_poi.get_key())) self.log.debug('finished populating at '.format(time.time())) def change_refind_method(self): """ Make appropriate changes in the GUI to reflect the newly chosen refind method.""" if self._mw.refind_method_ComboBox.currentText( ) == 'position optimisation': self._mw.offset_anchor_ComboBox.setEnabled(False) elif self._mw.refind_method_ComboBox.currentText() == 'offset anchor': self.log.error( "Anchor method not fully implemented yet. " "Feel free to fix this method. Using position optimisation instead." ) self._mw.offset_anchor_ComboBox.setEnabled(True) else: # TODO: throw an error self.log.debug('error 123') def set_roi_name(self): """ Set the name of a ROI (useful when saving).""" self._poi_manager_logic.roi_name = self._mw.roi_name_LineEdit.text( ).replace(" ", "_") def change_poi_name(self): """ Change the name of a poi.""" newname = self._mw.poi_name_LineEdit.text() self._poi_manager_logic.rename_poi( poikey=self._poi_manager_logic.active_poi.get_key(), name=newname) # After POI name is changed, empty name field self._mw.poi_name_LineEdit.setText('') def handle_active_poi_ComboBox_index_change(self): """ Handle the change of index in the active POI combobox.""" key = self._mw.active_poi_ComboBox.itemData( self._mw.active_poi_ComboBox.currentIndex()) self._poi_manager_logic.set_active_poi(poikey=key) self._redraw_poi_markers( ) # todo when line 660 signal in logic is done, this is not necessary def select_poi_from_marker(self, poikey=None): """ Process the selection of a POI from click on POImark.""" # Keep track of selected POI self._poi_manager_logic.set_active_poi(poikey=poikey) # # Set the selected POI in the combobox # self._mw.active_poi_ComboBox.setCurrentIndex(self._mw.active_poi_ComboBox.findData(poikey)) # self._redraw_poi_markers() def update_poi_pos(self): if self._poi_manager_logic.active_poi is None: self.log.warning("No POI selected.") else: if self._mw.refind_method_ComboBox.currentText( ) == 'position optimisation': self._poi_manager_logic.optimise_poi( poikey=self._poi_manager_logic.active_poi.get_key()) elif self._mw.refind_method_ComboBox.currentText( ) == 'offset anchor': anchor_key = self._mw.offset_anchor_ComboBox.itemData( self._mw.offset_anchor_ComboBox.currentIndex()) self._poi_manager_logic.optimise_poi( poikey=self._poi_manager_logic.active_poi.get_key(), anchorkey=anchor_key) def toggle_follow(self): if self._mw.goto_poi_after_update_checkBox.isChecked(): self._poi_manager_logic.go_to_crosshair_after_refocus = False else: self._poi_manager_logic.go_to_crosshair_after_refocus = True def _update_timer(self): self._mw.time_till_next_update_ProgressBar.setValue( self._poi_manager_logic.time_left) def set_track_period(self): """ Change the progress bar and update the timer duration.""" new_track_period = self._mw.track_period_SpinBox.value() self._poi_manager_logic.set_periodic_optimize_duration( duration=new_track_period) def _track_period_changed(self): """ Reflect the changed track period in the GUI elements. """ new_track_period = self._poi_manager_logic.timer_duration # Set the new maximum for the progress bar self._mw.time_till_next_update_ProgressBar.setMaximum(new_track_period) # If the tracker is not active, then set the value of the progress bar to the # new maximum if not self._mw.track_poi_Action.isChecked(): self._mw.time_till_next_update_ProgressBar.setValue( new_track_period) def _redraw_clocktime_ticks(self): """If duration is displayed, reset ticks to default. Otherwise, create and update custom date/time ticks to the new axis range. """ myAxisItem = self._mw.sample_shift_ViewWidget.plotItem.axes['bottom'][ 'item'] # if duration display, reset to default ticks if self._mw.display_shift_vs_duration_RadioButton.isChecked(): myAxisItem.setTicks(None) # otherwise, convert tick strings to clock format else: # determine size of the sample shift bottom axis item in pixels bounds = myAxisItem.mapRectFromParent(myAxisItem.geometry()) span = (bounds.topLeft(), bounds.topRight()) lengthInPixels = (span[1] - span[0]).manhattanLength() if lengthInPixels == 0: return -1 if myAxisItem.range[0] < 0: return -1 default_ticks = myAxisItem.tickValues(myAxisItem.range[0], myAxisItem.range[1], lengthInPixels) newticks = [] for i, tick_level in enumerate(default_ticks): newticks_this_level = [] ticks = tick_level[1] for ii, tick in enumerate(ticks): # For major ticks, include date if i == 0: string = time.strftime("%H:%M (%d.%m.)", time.localtime(tick * 3600)) # (the axis is plotted in hours to get naturally better placed ticks.) # for middle and minor ticks, just display clock time else: string = time.strftime("%H:%M", time.localtime(tick * 3600)) newticks_this_level.append((tick, string)) newticks.append(newticks_this_level) myAxisItem.setTicks(newticks) return 0 def _redraw_sample_shift(self): # Get trace data and calculate shifts in x,y,z poi_trace = self._poi_manager_logic.poi_list[ 'sample'].get_position_history() # If duration display is checked, subtract initial time and convert to # mins or hours as appropriate if self._mw.display_shift_vs_duration_RadioButton.isChecked(): time_shift_data = (poi_trace[:, 0] - poi_trace[0, 0]) if np.max(time_shift_data) < 300: self._mw.sample_shift_ViewWidget.setLabel('bottom', 'Time elapsed', units='s') elif np.max(time_shift_data) < 7200: time_shift_data = time_shift_data / 60.0 self._mw.sample_shift_ViewWidget.setLabel('bottom', 'Time elapsed', units='min') else: time_shift_data = time_shift_data / 3600.0 self._mw.sample_shift_ViewWidget.setLabel('bottom', 'Time elapsed', units='hr') # Otherwise, take the actual time but divide by 3600 so that tickmarks # automatically fall on whole hours else: time_shift_data = poi_trace[:, 0] / 3600.0 self._mw.sample_shift_ViewWidget.setLabel('bottom', 'Time', units='') # Subtract initial position to get shifts x_shift_data = (poi_trace[:, 1] - poi_trace[0, 1]) y_shift_data = (poi_trace[:, 2] - poi_trace[0, 2]) z_shift_data = (poi_trace[:, 3] - poi_trace[0, 3]) # Plot data self.x_shift_plot.setData(time_shift_data, x_shift_data) self.y_shift_plot.setData(time_shift_data, y_shift_data) self.z_shift_plot.setData(time_shift_data, z_shift_data) self._redraw_clocktime_ticks() def _redraw_poi_markers(self): self.log.debug('starting redraw_poi_markers {0}'.format(time.time())) for key in self._poi_manager_logic.get_all_pois(): if key is not 'crosshair' and key is not 'sample': position = self._poi_manager_logic.get_poi_position(poikey=key) position = position[:2] if key in self._markers.keys(): self._markers[key].set_position(position) self._markers[key].deselect() else: # Create Region of Interest as marker: marker = PoiMark(position, poi=self._poi_manager_logic.poi_list[key], click_action=self.select_poi_from_marker, movable=False, scaleSnap=False, snapSize=1.0e-6) # Add to the Map Widget marker.add_to_viewwidget(self._mw.roi_map_ViewWidget) self._markers[key] = marker if self._poi_manager_logic.active_poi is not None: active_poi_key = self._poi_manager_logic.active_poi.get_key() self._markers[active_poi_key].select() cur_poi_pos = self._poi_manager_logic.get_poi_position( poikey=active_poi_key) self._mw.poi_coords_label.setText( '({0:.2r}m, {1:.2r}m, {2:.2r}m)'.format( ScaledFloat(cur_poi_pos[0]), ScaledFloat(cur_poi_pos[1]), ScaledFloat(cur_poi_pos[2]))) self.log.debug('finished redraw at {0}'.format(time.time())) def make_new_roi(self): """ Start new ROI by removing all POIs and resetting the sample history.""" for key in self._poi_manager_logic.get_all_pois(): if key is not 'crosshair' and key is not 'sample': self._markers[key].delete_from_viewwidget() del self._markers self._markers = dict() self._poi_manager_logic.reset_roi() self.populate_poi_list() def save_roi(self): """ Save ROI to file.""" self._poi_manager_logic.save_poi_map_as_roi() def load_roi(self): """ Load a saved ROI from file.""" this_file = QtWidgets.QFileDialog.getOpenFileName( self._mw, str("Open ROI"), None, str("Data files (*.dat)"))[0] self._poi_manager_logic.load_roi_from_file(filename=this_file) self.populate_poi_list() def open_reorient_roi_dialog(self): """ Open the dialog for reorienting the ROI. """ self._rrd.show() def ref_a_at_crosshair(self): """ Set the newpos for ref A from the current crosshair position. """ # TODO: get the range for these spinboxes from the hardware scanner range! self._rrd.ref_a_x_pos_DoubleSpinBox.setValue( self._confocal_logic.get_position()[0]) self._rrd.ref_a_y_pos_DoubleSpinBox.setValue( self._confocal_logic.get_position()[1]) self._rrd.ref_a_z_pos_DoubleSpinBox.setValue( self._confocal_logic.get_position()[2]) def ref_b_at_crosshair(self): """ Set the newpos for ref B from the current crosshair position. """ self._rrd.ref_b_x_pos_DoubleSpinBox.setValue( self._confocal_logic.get_position()[0]) self._rrd.ref_b_y_pos_DoubleSpinBox.setValue( self._confocal_logic.get_position()[1]) self._rrd.ref_b_z_pos_DoubleSpinBox.setValue( self._confocal_logic.get_position()[2]) def ref_c_at_crosshair(self): """ Set the newpos for ref C from the current crosshair position. """ self._rrd.ref_c_x_pos_DoubleSpinBox.setValue( self._confocal_logic.get_position()[0]) self._rrd.ref_c_y_pos_DoubleSpinBox.setValue( self._confocal_logic.get_position()[1]) self._rrd.ref_c_z_pos_DoubleSpinBox.setValue( self._confocal_logic.get_position()[2]) def do_roi_reorientation(self): """Pass the old and new positions of refs A, B, C to PoiManager Logic to reorient every POI in the ROI. """ ref_a_coords, ref_b_coords, ref_c_coords, ref_a_newpos, ref_b_newpos, ref_c_newpos = self._read_reorient_roi_dialog_values( ) self._poi_manager_logic.reorient_roi(ref_a_coords, ref_b_coords, ref_c_coords, ref_a_newpos, ref_b_newpos, ref_c_newpos) # Clear the values in the Reorient Roi Dialog in case it is needed again self.reset_reorientation_dialog() def _read_reorient_roi_dialog_values(self): """ This reads the values from reorient ROI Dialog, and returns them. """ # Get POI keys for the chosen ref points ref_a_key = self._rrd.ref_a_poi_ComboBox.itemData( self._rrd.ref_a_poi_ComboBox.currentIndex()) ref_b_key = self._rrd.ref_b_poi_ComboBox.itemData( self._rrd.ref_b_poi_ComboBox.currentIndex()) ref_c_key = self._rrd.ref_c_poi_ComboBox.itemData( self._rrd.ref_c_poi_ComboBox.currentIndex()) # Get the old coords for these refs ref_a_coords = np.array( self._poi_manager_logic.poi_list[ref_a_key].get_coords_in_sample()) ref_b_coords = np.array( self._poi_manager_logic.poi_list[ref_b_key].get_coords_in_sample()) ref_c_coords = np.array( self._poi_manager_logic.poi_list[ref_c_key].get_coords_in_sample()) ref_a_newpos = np.array([ self._rrd.ref_a_x_pos_DoubleSpinBox.value(), self._rrd.ref_a_y_pos_DoubleSpinBox.value(), self._rrd.ref_a_z_pos_DoubleSpinBox.value() ]) ref_b_newpos = np.array([ self._rrd.ref_b_x_pos_DoubleSpinBox.value(), self._rrd.ref_b_y_pos_DoubleSpinBox.value(), self._rrd.ref_b_z_pos_DoubleSpinBox.value() ]) ref_c_newpos = np.array([ self._rrd.ref_c_x_pos_DoubleSpinBox.value(), self._rrd.ref_c_y_pos_DoubleSpinBox.value(), self._rrd.ref_c_z_pos_DoubleSpinBox.value() ]) return ref_a_coords, ref_b_coords, ref_c_coords, ref_a_newpos * 1e-6, ref_b_newpos * 1e-6, ref_c_newpos * 1e-6 def reset_reorientation_dialog(self): """ Reset all the values in the reorient roi dialog. """ self._rrd.ref_a_x_pos_DoubleSpinBox.setValue(0) self._rrd.ref_a_y_pos_DoubleSpinBox.setValue(0) self._rrd.ref_a_z_pos_DoubleSpinBox.setValue(0) self._rrd.ref_b_x_pos_DoubleSpinBox.setValue(0) self._rrd.ref_b_y_pos_DoubleSpinBox.setValue(0) self._rrd.ref_b_z_pos_DoubleSpinBox.setValue(0) self._rrd.ref_c_x_pos_DoubleSpinBox.setValue(0) self._rrd.ref_c_y_pos_DoubleSpinBox.setValue(0) self._rrd.ref_c_z_pos_DoubleSpinBox.setValue(0) def reorientation_sanity_check(self): """ Calculate the difference in length between edges of old triangle defined by refs A, B, C and the new triangle. """ # Get set of positions from GUI ref_a_coords, ref_b_coords, ref_c_coords, ref_a_newpos, ref_b_newpos, ref_c_newpos = self._read_reorient_roi_dialog_values( ) # Calculate the difference in side lengths AB, BC, CA between the old triangle and the new triangle delta_ab = np.linalg.norm(ref_b_coords - ref_a_coords) - np.linalg.norm(ref_b_newpos - ref_a_newpos) delta_bc = np.linalg.norm(ref_c_coords - ref_b_coords) - np.linalg.norm(ref_c_newpos - ref_b_newpos) delta_ca = np.linalg.norm(ref_a_coords - ref_c_coords) - np.linalg.norm(ref_a_newpos - ref_c_newpos) # Write to the GUI self._rrd.length_difference_ab_Label.setText(str(delta_ab)) self._rrd.length_difference_bc_Label.setText(str(delta_bc)) self._rrd.length_difference_ca_Label.setText(str(delta_ca)) def do_autofind_poi_procedure(self): """Run the autofind_pois procedure in the POI Manager Logic to get all the POIs in the current ROI image.""" #Fixme: Add here the appropriate functionality self.log.error("Has to be implemented properly. Feel free to do it.") # # Get the thresholds from the user-chosen color bar range # cb_min, cb_max = self.determine_cb_range() # # this_min_threshold = cb_min + 0.3 * (cb_max - cb_min) # this_max_threshold = cb_max # # self._poi_manager_logic.autofind_pois(neighborhood_size=1, min_threshold=this_min_threshold, max_threshold=this_max_threshold) def optimize_roi(self): """Run the autofind_pois procedure in the POI Manager Logic to get all the POIs in the current ROI image.""" #Fixme: Add here the appropriate functionality self.log.error("Not implemented yet. Feel free to help!")
class PoiManagerGui(GUIBase): """ This is the GUI Class for PoiManager """ _modclass = 'PoiManagerGui' _modtype = 'gui' # declare connectors poimanagerlogic = Connector(interface='PoiManagerLogic') scannerlogic = Connector(interface='ConfocalLogic') # declare signals sigTrackPeriodChanged = QtCore.Signal(float) sigPoiNameChanged = QtCore.Signal(str) sigPoiNameTagChanged = QtCore.Signal(str) sigRoiNameChanged = QtCore.Signal(str) sigAddPoiByClick = QtCore.Signal(np.ndarray) def __init__(self, config, **kwargs): super().__init__(config=config, **kwargs) self._mw = None # QMainWindow handle self.roi_image = None # pyqtgraph PlotImage for ROI scan image self.roi_cb = None # The qudi colorbar to use with roi_image self.x_shift_plot = None # pyqtgraph PlotDataItem for ROI history plot self.y_shift_plot = None # pyqtgraph PlotDataItem for ROI history plot self.z_shift_plot = None # pyqtgraph PlotDataItem for ROI history plot self._markers = dict() # dict to hold handles for the POI markers self._mouse_moved_proxy = None # Signal proxy to limit mousMoved event rate self.__poi_selector_active = False # Flag indicating if the poi selector is active return def on_activate(self): """ Initializes the overall GUI, and establishes the connectors. This method executes the init methods for each of the GUIs. """ self._markers = dict() self._mw = PoiManagerMainWindow() # Configuring the dock widgets. # All our gui elements are dockable, so there should be no "central" widget. self._mw.centralwidget.hide() self._mw.setDockNestingEnabled(True) # Add validator to LineEdits self._mw.roi_name_LineEdit.setValidator(NameValidator()) self._mw.poi_name_LineEdit.setValidator(NameValidator()) self._mw.poi_nametag_LineEdit.setValidator( NameValidator(empty_allowed=True)) # Initialize plots self.__init_roi_scan_image() self.__init_roi_history_plot() # Initialize refocus timer self.update_refocus_timer( self.poimanagerlogic().module_state() == 'locked', self.poimanagerlogic().refocus_period, self.poimanagerlogic().refocus_period) # Initialize POIs self._update_pois(self.poimanagerlogic().poi_positions) # Initialize ROI name self._update_roi_name(self.poimanagerlogic().roi_name) # Initialize POI nametag self._update_poi_nametag(self.poimanagerlogic().poi_nametag) # Distance Measurement: # Introducing a SignalProxy will limit the rate of signals that get fired. self._mouse_moved_proxy = pg.SignalProxy( signal=self.roi_image.scene().sigMouseMoved, rateLimit=30, slot=self.mouse_moved_callback) # Connect signals self.__connect_internal_signals() self.__connect_update_signals_from_logic() self.__connect_control_signals_to_logic() self._mw.show() return def on_deactivate(self): """ De-initialisation performed during deactivation of the module. """ self.toggle_poi_selector(False) self.__disconnect_control_signals_to_logic() self.__disconnect_update_signals_from_logic() self.__disconnect_internal_signals() self._mw.close() def __init_roi_scan_image(self): # Get the color scheme my_colors = ColorScaleInferno() # Setting up display of ROI xy scan image self.roi_image = pg.ImageItem(axisOrder='row-major', lut=my_colors.lut) self._mw.roi_map_ViewWidget.addItem(self.roi_image) self._mw.roi_map_ViewWidget.setLabel('bottom', 'X position', units='m') self._mw.roi_map_ViewWidget.setLabel('left', 'Y position', units='m') self._mw.roi_map_ViewWidget.setAspectLocked(lock=True, ratio=1.0) # Set up color bar self.roi_cb = ColorBar(my_colors.cmap_normed, 100, 0, 100000) self._mw.roi_cb_ViewWidget.addItem(self.roi_cb) self._mw.roi_cb_ViewWidget.hideAxis('bottom') self._mw.roi_cb_ViewWidget.setLabel('left', 'Fluorescence', units='c/s') self._mw.roi_cb_ViewWidget.setMouseEnabled(x=False, y=False) # Get scan image from logic and update initialize plot self._update_scan_image(self.poimanagerlogic().roi_scan_image, self.poimanagerlogic().roi_scan_image_extent) return def __init_roi_history_plot(self): # Setting up display of sample shift plot self.x_shift_plot = pg.PlotDataItem(x=[0], y=[0], pen=pg.mkPen( palette.c1, style=QtCore.Qt.DotLine), symbol='o', symbolPen=palette.c1, symbolBrush=palette.c1, symbolSize=5, name='x') self.y_shift_plot = pg.PlotDataItem(x=[0], y=[0], pen=pg.mkPen( palette.c2, style=QtCore.Qt.DotLine), symbol='s', symbolPen=palette.c2, symbolBrush=palette.c2, symbolSize=5, name='y') self.z_shift_plot = pg.PlotDataItem(x=[0], y=[0], pen=pg.mkPen( palette.c3, style=QtCore.Qt.DotLine), symbol='t', symbolPen=palette.c3, symbolBrush=palette.c3, symbolSize=5, name='z') self._mw.sample_shift_ViewWidget.addLegend() # Add the plot to the ViewWidget defined in the UI file self._mw.sample_shift_ViewWidget.addItem(self.x_shift_plot) self._mw.sample_shift_ViewWidget.addItem(self.y_shift_plot) self._mw.sample_shift_ViewWidget.addItem(self.z_shift_plot) # Label axes self._mw.sample_shift_ViewWidget.setLabel('bottom', 'Time', units='s') self._mw.sample_shift_ViewWidget.setLabel('left', 'Sample shift', units='m') self._update_roi_history(self.poimanagerlogic().roi_pos_history) return def __connect_update_signals_from_logic(self): self.poimanagerlogic().sigRefocusTimerUpdated.connect( self.update_refocus_timer, QtCore.Qt.QueuedConnection) self.poimanagerlogic().sigPoiUpdated.connect( self.update_poi, QtCore.Qt.QueuedConnection) self.poimanagerlogic().sigActivePoiUpdated.connect( self.update_active_poi, QtCore.Qt.QueuedConnection) self.poimanagerlogic().sigRoiUpdated.connect( self.update_roi, QtCore.Qt.QueuedConnection) self.poimanagerlogic().sigRefocusStateUpdated.connect( self.update_refocus_state, QtCore.Qt.QueuedConnection) return def __disconnect_update_signals_from_logic(self): self.poimanagerlogic().sigRefocusTimerUpdated.disconnect() self.poimanagerlogic().sigPoiUpdated.disconnect() self.poimanagerlogic().sigActivePoiUpdated.disconnect() self.poimanagerlogic().sigRoiUpdated.disconnect() self.poimanagerlogic().sigRefocusStateUpdated.disconnect() return def __connect_control_signals_to_logic(self): self._mw.new_poi_Action.triggered.connect( self.poimanagerlogic().add_poi, QtCore.Qt.QueuedConnection) self._mw.goto_poi_Action.triggered.connect( self.poimanagerlogic().go_to_poi, QtCore.Qt.QueuedConnection) self._mw.new_roi_Action.triggered.connect( self.poimanagerlogic().reset_roi, QtCore.Qt.QueuedConnection) self._mw.refind_poi_Action.triggered.connect( self.poimanagerlogic().optimise_poi_position, QtCore.Qt.QueuedConnection) self._mw.get_confocal_image_PushButton.clicked.connect( self.poimanagerlogic().set_scan_image, QtCore.Qt.QueuedConnection) self._mw.set_poi_PushButton.clicked.connect( self.poimanagerlogic().add_poi, QtCore.Qt.QueuedConnection) self._mw.delete_last_pos_Button.clicked.connect( self.poimanagerlogic().delete_history_entry, QtCore.Qt.QueuedConnection) self._mw.manual_update_poi_PushButton.clicked.connect( self.poimanagerlogic().move_roi_from_poi_position, QtCore.Qt.QueuedConnection) self._mw.move_poi_PushButton.clicked.connect( self.poimanagerlogic().set_poi_anchor_from_position, QtCore.Qt.QueuedConnection) self._mw.delete_poi_PushButton.clicked.connect( self.poimanagerlogic().delete_poi, QtCore.Qt.QueuedConnection) self._mw.active_poi_ComboBox.activated[str].connect( self.poimanagerlogic().set_active_poi, QtCore.Qt.QueuedConnection) self._mw.goto_poi_after_update_checkBox.stateChanged.connect( self.poimanagerlogic().set_move_scanner_after_optimise, QtCore.Qt.QueuedConnection) self._mw.track_poi_Action.triggered.connect( self.poimanagerlogic().toggle_periodic_refocus, QtCore.Qt.QueuedConnection) self.sigTrackPeriodChanged.connect( self.poimanagerlogic().set_refocus_period, QtCore.Qt.QueuedConnection) self.sigRoiNameChanged.connect(self.poimanagerlogic().rename_roi, QtCore.Qt.QueuedConnection) self.sigPoiNameChanged.connect(self.poimanagerlogic().rename_poi, QtCore.Qt.QueuedConnection) self.sigPoiNameTagChanged.connect( self.poimanagerlogic().set_poi_nametag, QtCore.Qt.QueuedConnection) self.sigAddPoiByClick.connect(self.poimanagerlogic().add_poi, QtCore.Qt.QueuedConnection) return def __disconnect_control_signals_to_logic(self): self._mw.new_poi_Action.triggered.disconnect() self._mw.goto_poi_Action.triggered.disconnect() self._mw.new_roi_Action.triggered.disconnect() self._mw.refind_poi_Action.triggered.disconnect() self._mw.get_confocal_image_PushButton.clicked.disconnect() self._mw.set_poi_PushButton.clicked.disconnect() self._mw.delete_last_pos_Button.clicked.disconnect() self._mw.manual_update_poi_PushButton.clicked.disconnect() self._mw.move_poi_PushButton.clicked.disconnect() self._mw.delete_poi_PushButton.clicked.disconnect() self._mw.active_poi_ComboBox.activated[str].disconnect() self._mw.goto_poi_after_update_checkBox.stateChanged.disconnect() self._mw.track_poi_Action.triggered.disconnect() self.sigTrackPeriodChanged.disconnect() self.sigRoiNameChanged.disconnect() self.sigPoiNameChanged.disconnect() self.sigPoiNameTagChanged.disconnect() self.sigAddPoiByClick.disconnect() for marker in self._markers.values(): marker.sigPoiSelected.disconnect() return def __connect_internal_signals(self): self._mw.track_period_SpinBox.editingFinished.connect( self.track_period_changed) self._mw.roi_name_LineEdit.editingFinished.connect( self.roi_name_changed) self._mw.poi_name_LineEdit.returnPressed.connect(self.poi_name_changed) self._mw.poi_nametag_LineEdit.editingFinished.connect( self.poi_nametag_changed) self._mw.save_roi_Action.triggered.connect(self.save_roi) self._mw.load_roi_Action.triggered.connect(self.load_roi) self._mw.poi_selector_Action.toggled.connect(self.toggle_poi_selector) self._mw.roi_cb_centiles_RadioButton.toggled.connect(self.update_cb) self._mw.roi_cb_manual_RadioButton.toggled.connect(self.update_cb) self._mw.roi_cb_min_SpinBox.valueChanged.connect( self.update_cb_absolute) self._mw.roi_cb_max_SpinBox.valueChanged.connect( self.update_cb_absolute) self._mw.roi_cb_low_percentile_DoubleSpinBox.valueChanged.connect( self.update_cb_centiles) self._mw.roi_cb_high_percentile_DoubleSpinBox.valueChanged.connect( self.update_cb_centiles) return def __disconnect_internal_signals(self): self._mw.track_period_SpinBox.editingFinished.disconnect() self._mw.roi_name_LineEdit.editingFinished.disconnect() self._mw.poi_name_LineEdit.returnPressed.disconnect() self._mw.poi_nametag_LineEdit.editingFinished.disconnect() self._mw.save_roi_Action.triggered.disconnect() self._mw.load_roi_Action.triggered.disconnect() self._mw.poi_selector_Action.toggled.disconnect() self._mw.roi_cb_centiles_RadioButton.toggled.disconnect() self._mw.roi_cb_manual_RadioButton.toggled.disconnect() self._mw.roi_cb_min_SpinBox.valueChanged.disconnect() self._mw.roi_cb_max_SpinBox.valueChanged.disconnect() self._mw.roi_cb_low_percentile_DoubleSpinBox.valueChanged.disconnect() self._mw.roi_cb_high_percentile_DoubleSpinBox.valueChanged.disconnect() return def show(self): """Make main window visible and put it above all other windows. """ QtWidgets.QMainWindow.show(self._mw) self._mw.activateWindow() self._mw.raise_() @QtCore.Slot(object) def mouse_moved_callback(self, event): """ Handles any mouse movements inside the image. @param event: Event that signals the new mouse movement. This should be of type QPointF. Gets the mouse position, converts it to a position scaled to the image axis and than calculates and updated the position to the current POI. """ # converts the absolute mouse position to a position relative to the axis mouse_pos = self.roi_image.getViewBox().mapSceneToView(event[0]) # only calculate distance, if a POI is selected active_poi = self.poimanagerlogic().active_poi if active_poi: poi_pos = self.poimanagerlogic().get_poi_position(active_poi) dx = ScaledFloat(mouse_pos.x() - poi_pos[0]) dy = ScaledFloat(mouse_pos.y() - poi_pos[1]) d_total = ScaledFloat( np.sqrt((mouse_pos.x() - poi_pos[0])**2 + (mouse_pos.y() - poi_pos[1])**2)) self._mw.poi_distance_label.setText( '{0:.2r}m ({1:.2r}m, {2:.2r}m)'.format(d_total, dx, dy)) else: self._mw.poi_distance_label.setText('? (?, ?)') pass @QtCore.Slot(bool) def toggle_poi_selector(self, is_active): if is_active != self._mw.poi_selector_Action.isChecked(): self._mw.poi_selector_Action.blockSignals(True) self._mw.poi_selector_Action.setChecked(is_active) self._mw.poi_selector_Action.blockSignals(False) if is_active != self.__poi_selector_active: if is_active: self.roi_image.scene().sigMouseClicked.connect( self.create_poi_from_click) self.roi_image.setCursor(QtCore.Qt.CrossCursor) else: self.roi_image.scene().sigMouseClicked.disconnect() self.roi_image.setCursor(QtCore.Qt.ArrowCursor) self.__poi_selector_active = is_active return @QtCore.Slot(object) def create_poi_from_click(self, event): # Only create new POI if the mouse click event has not been accepted by some other means # In our case this is most likely the POI marker to select the active POI from. if not event.accepted: # Z position from ROI origin, X and Y positions from click event new_pos = self.poimanagerlogic().roi_origin cursor_pos = self.roi_image.getViewBox().mapSceneToView( event.scenePos()) new_pos[0] = cursor_pos.x() new_pos[1] = cursor_pos.y() # Check if position lies within axis boundaries. x_axis_box = self._mw.roi_map_ViewWidget.getAxis( 'bottom').sceneBoundingRect() y_axis_box = self._mw.roi_map_ViewWidget.getAxis( 'left').sceneBoundingRect() min_x = self.roi_image.getViewBox().mapSceneToView( y_axis_box.bottomRight()).x() min_y = self.roi_image.getViewBox().mapSceneToView( x_axis_box.topLeft()).y() if new_pos[0] <= min_x or new_pos[1] <= min_y: return # Check if position is within scan image boundaries image_extent = self.poimanagerlogic().roi_scan_image_extent if image_extent is None: return if image_extent[0][0] > new_pos[0] or image_extent[0][1] < new_pos[ 0]: return if image_extent[1][0] > new_pos[1] or image_extent[1][1] < new_pos[ 1]: return event.accept() self.sigAddPoiByClick.emit(new_pos) return @QtCore.Slot(dict) def update_roi(self, roi_dict): if not isinstance(roi_dict, dict): self.log.error( 'ROI parameters to update must be given in a single dictionary.' ) return if 'name' in roi_dict: self._update_roi_name(name=roi_dict['name']) if 'poi_nametag' in roi_dict: self._update_poi_nametag(tag=roi_dict['poi_nametag']) if 'history' in roi_dict: self._update_roi_history(history=roi_dict['history']) if 'scan_image' in roi_dict and 'scan_image_extent' in roi_dict: self._update_scan_image(scan_image=roi_dict['scan_image'], image_extent=roi_dict['scan_image_extent']) if 'pois' in roi_dict: self._update_pois(poi_dict=roi_dict['pois']) return @QtCore.Slot(bool, float, float) def update_refocus_timer(self, is_active, period, time_until_refocus): if not self._mw.track_period_SpinBox.hasFocus(): self._mw.track_period_SpinBox.blockSignals(True) self._mw.track_period_SpinBox.setValue(period) self._mw.track_period_SpinBox.blockSignals(False) self._mw.track_poi_Action.blockSignals(True) self._mw.time_till_next_update_ProgressBar.blockSignals(True) self._mw.track_poi_Action.setChecked(is_active) self._mw.time_till_next_update_ProgressBar.setMaximum(period) self._mw.time_till_next_update_ProgressBar.setValue(time_until_refocus) self._mw.time_till_next_update_ProgressBar.blockSignals(False) self._mw.track_poi_Action.blockSignals(False) return @QtCore.Slot(bool) def update_refocus_state(self, is_active): self._mw.refind_poi_Action.setEnabled(not is_active) return @QtCore.Slot(str, str, np.ndarray) def update_poi(self, old_name, new_name, position): # Handle changed names and deleted/added POIs if old_name != new_name: self._mw.active_poi_ComboBox.blockSignals(True) # Remember current text text_active_poi = self._mw.active_poi_ComboBox.currentText() # sort POI names and repopulate ComboBoxes self._mw.active_poi_ComboBox.clear() poi_names = natural_sort(self.poimanagerlogic().poi_names) self._mw.active_poi_ComboBox.addItems(poi_names) if text_active_poi == old_name: self._mw.active_poi_ComboBox.setCurrentText(new_name) else: self._mw.active_poi_ComboBox.setCurrentText(text_active_poi) self._mw.active_poi_ComboBox.blockSignals(False) # Delete/add/update POI marker to image if not old_name: # POI has been added self._add_poi_marker(name=new_name, position=position) elif not new_name: # POI has been deleted self._remove_poi_marker(name=old_name) else: # POI has been renamed and/or changed position size = self.poimanagerlogic().optimise_xy_size * np.sqrt(2) self._markers[old_name].set_name(new_name) self._markers[new_name] = self._markers.pop(old_name) self._markers[new_name].setSize((size, size)) self._markers[new_name].set_position(position[:2]) active_poi = self._mw.active_poi_ComboBox.currentText() if active_poi: self._markers[active_poi].select() return @QtCore.Slot(str) def update_active_poi(self, name): # Deselect current marker for marker in self._markers.values(): if marker.selected: marker.deselect() break # Unselect POI if name is None or empty str self._mw.active_poi_ComboBox.blockSignals(True) if not name: self._mw.active_poi_ComboBox.setCurrentIndex(-1) else: self._mw.active_poi_ComboBox.setCurrentText(name) self._mw.active_poi_ComboBox.blockSignals(False) if name: active_poi_pos = self.poimanagerlogic().get_poi_position(name) else: active_poi_pos = np.zeros(3) self._mw.poi_coords_label.setText( '({0:.2r}m, {1:.2r}m, {2:.2r}m)'.format( ScaledFloat(active_poi_pos[0]), ScaledFloat(active_poi_pos[1]), ScaledFloat(active_poi_pos[2]))) if name in self._markers: self._markers[name].set_radius( self.poimanagerlogic().optimise_xy_size / np.sqrt(2)) self._markers[name].select() return @QtCore.Slot() def track_period_changed(self): self.sigTrackPeriodChanged.emit(self._mw.track_period_SpinBox.value()) return @QtCore.Slot() def roi_name_changed(self): """ Set the name of the current ROI.""" self.sigRoiNameChanged.emit(self._mw.roi_name_LineEdit.text()) return @QtCore.Slot() def poi_name_changed(self): """ Change the name of the active poi.""" new_name = self._mw.poi_name_LineEdit.text() if self._mw.active_poi_ComboBox.currentText( ) == new_name or not new_name: return self.sigPoiNameChanged.emit(new_name) # After POI name is changed, empty name field self._mw.poi_name_LineEdit.blockSignals(True) self._mw.poi_name_LineEdit.setText('') self._mw.poi_name_LineEdit.blockSignals(False) return @QtCore.Slot() def poi_nametag_changed(self): self.sigPoiNameTagChanged.emit(self._mw.poi_nametag_LineEdit.text()) return @QtCore.Slot() def save_roi(self): """ Save ROI to file.""" roi_name = self._mw.roi_name_LineEdit.text() self.poimanagerlogic().rename_roi(roi_name) self.poimanagerlogic().save_roi() return @QtCore.Slot() def load_roi(self): """ Load a saved ROI from file.""" this_file = QtWidgets.QFileDialog.getOpenFileName( self._mw, 'Open ROI', self.poimanagerlogic().data_directory, 'Data files (*.dat)')[0] if this_file: self.poimanagerlogic().load_roi(complete_path=this_file) return @QtCore.Slot() def update_cb_centiles(self): if not self._mw.roi_cb_centiles_RadioButton.isChecked(): self._mw.roi_cb_centiles_RadioButton.toggle() else: self.update_cb() return @QtCore.Slot() def update_cb_absolute(self): if not self._mw.roi_cb_manual_RadioButton.isChecked(): self._mw.roi_cb_manual_RadioButton.toggle() else: self.update_cb() return @QtCore.Slot() def update_cb(self): image = self.poimanagerlogic().roi_scan_image if image is None: return cb_range = self.get_cb_range(image) self.roi_image.setLevels(cb_range) self.roi_cb.refresh_colorbar(*cb_range) return def _update_scan_image(self, scan_image, image_extent): """ @param scan_image: @param image_extent: """ if scan_image is None or image_extent is None: self._mw.roi_map_ViewWidget.removeItem(self.roi_image) return elif self.roi_image not in self._mw.roi_map_ViewWidget.items(): self._mw.roi_map_ViewWidget.addItem(self.roi_image) self.roi_image.setImage(image=scan_image) (x_min, x_max), (y_min, y_max) = image_extent self.roi_image.getViewBox().enableAutoRange() self.roi_image.setRect( QtCore.QRectF(x_min, y_min, x_max - x_min, y_max - y_min)) self.update_cb() return def _update_roi_name(self, name): self._mw.roi_name_LineEdit.blockSignals(True) self._mw.roi_name_LineEdit.setText(name) self._mw.roi_name_LineEdit.blockSignals(False) return def _update_poi_nametag(self, tag): if tag is None: tag = '' self._mw.poi_nametag_LineEdit.blockSignals(True) self._mw.poi_nametag_LineEdit.setText(tag) self._mw.poi_nametag_LineEdit.blockSignals(False) return def _update_roi_history(self, history=None): if history is None: history = self.poimanagerlogic().roi_pos_history if history.shape[1] != 4: self.log.error('ROI history must be an array of type float[][4].') return max_time = np.max(history[:, 0]) if max_time < 300: self._mw.sample_shift_ViewWidget.setLabel('bottom', 'Time', units='s') time_arr = history[:, 0] elif max_time < 7200: self._mw.sample_shift_ViewWidget.setLabel('bottom', 'Time', units='min') time_arr = history[:, 0] / 60 elif max_time < 172800: self._mw.sample_shift_ViewWidget.setLabel('bottom', 'Time', units='h') time_arr = history[:, 0] / 3600 else: self._mw.sample_shift_ViewWidget.setLabel('bottom', 'Time', units='d') time_arr = history[:, 0] / 86400 self.x_shift_plot.setData(time_arr, history[:, 1]) self.y_shift_plot.setData(time_arr, history[:, 2]) self.z_shift_plot.setData(time_arr, history[:, 3]) return def _update_pois(self, poi_dict): """ Populate the dropdown box for selecting a poi. """ self._mw.active_poi_ComboBox.blockSignals(True) self._mw.active_poi_ComboBox.clear() poi_names = natural_sort(poi_dict) self._mw.active_poi_ComboBox.addItems(poi_names) # Get two list of POI names. One of those to delete and one of those to add old_poi_names = set(self._markers) new_poi_names = set(poi_names) names_to_delete = list(old_poi_names.difference(new_poi_names)) names_to_add = list(new_poi_names.difference(old_poi_names)) # Delete markers accordingly for name in names_to_delete: self._remove_poi_marker(name) # Update positions of all remaining markers size = self.poimanagerlogic().optimise_xy_size * np.sqrt(2) for name, marker in self._markers.items(): marker.setSize((size, size)) marker.set_position(poi_dict[name]) # Add new markers for name in names_to_add: self._add_poi_marker(name=name, position=poi_dict[name]) # If there is no active POI, set the combobox to nothing (-1) active_poi = self.poimanagerlogic().active_poi if active_poi in poi_names: self._mw.active_poi_ComboBox.setCurrentText(active_poi) self._markers[active_poi].select() active_poi_pos = poi_dict[active_poi] self._mw.poi_coords_label.setText( '({0:.2r}m, {1:.2r}m, {2:.2r}m)'.format( ScaledFloat(active_poi_pos[0]), ScaledFloat(active_poi_pos[1]), ScaledFloat(active_poi_pos[2]))) else: self._mw.active_poi_ComboBox.setCurrentIndex(-1) self._mw.active_poi_ComboBox.blockSignals(False) return def get_cb_range(self, image): """ Process UI input to determine color bar range""" # If "Centiles" is checked, adjust colour scaling automatically to centiles. # Otherwise, take user-defined values. if self._mw.roi_cb_centiles_RadioButton.isChecked(): low_centile = self._mw.roi_cb_low_percentile_DoubleSpinBox.value() high_centile = self._mw.roi_cb_high_percentile_DoubleSpinBox.value( ) cb_min = np.percentile(image, low_centile) cb_max = np.percentile(image, high_centile) else: cb_min = self._mw.roi_cb_min_SpinBox.value() cb_max = self._mw.roi_cb_max_SpinBox.value() return cb_min, cb_max def _add_poi_marker(self, name, position): """ Add a circular POI marker to the ROI scan image. """ if name: if name in self._markers: self.log.error( 'Unable to add POI marker to ROI image. POI marker already present.' ) return marker = PoiMarker(position=position[:2], view_widget=self._mw.roi_map_ViewWidget, poi_name=name, radius=self.poimanagerlogic().optimise_xy_size / np.sqrt(2), movable=False) # Add to the scan image widget marker.add_to_view_widget() marker.sigPoiSelected.connect( self.poimanagerlogic().set_active_poi, QtCore.Qt.QueuedConnection) self._markers[name] = marker return def _remove_poi_marker(self, name): """ Remove the POI marker for a POI that was deleted. """ if name in self._markers: self._markers[name].delete_from_view_widget() self._markers[name].sigPoiSelected.disconnect() del self._markers[name] return
class ODMRGui(GUIBase): """ This is the GUI Class for ODMR measurements """ _modclass = 'ODMRGui' _modtype = 'gui' # declare connectors _connectors = {'odmrlogic1': 'ODMRLogic', 'savelogic': 'SaveLogic'} sigStartOdmrScan = QtCore.Signal() sigStopOdmrScan = QtCore.Signal() sigContinueOdmrScan = QtCore.Signal() sigClearData = QtCore.Signal() sigCwMwOn = QtCore.Signal() sigMwOff = QtCore.Signal() sigMwPowerChanged = QtCore.Signal(float) sigMwCwParamsChanged = QtCore.Signal(float, float) sigMwSweepParamsChanged = QtCore.Signal(float, float, float, float) sigClockFreqChanged = QtCore.Signal(float) sigFitChanged = QtCore.Signal(str) sigNumberOfLinesChanged = QtCore.Signal(int) sigRuntimeChanged = QtCore.Signal(float) sigDoFit = QtCore.Signal(str) sigSaveMeasurement = QtCore.Signal(str, list, list) def __init__(self, config, **kwargs): super().__init__(config=config, **kwargs) self.log.info('The following configuration was found.') # checking for the right configuration for key in config.keys(): self.log.info('{0}: {1}'.format(key, config[key])) def on_activate(self): """ Definition, configuration and initialisation of the ODMR GUI. This init connects all the graphic modules, which were created in the *.ui file and configures the event handling between the modules. """ self._odmr_logic = self.get_connector('odmrlogic1') # Use the inherited class 'Ui_ODMRGuiUI' to create now the GUI element: self._mw = ODMRMainWindow() self._sd = ODMRSettingDialog() # Create a QSettings object for the mainwindow and store the actual GUI layout self.mwsettings = QtCore.QSettings("QUDI", "ODMR") self.mwsettings.setValue("geometry", self._mw.saveGeometry()) self.mwsettings.setValue("windowState", self._mw.saveState()) # Get hardware constraints to set limits for input widgets constraints = self._odmr_logic.get_hw_constraints() # Adjust range of scientific spinboxes above what is possible in Qt Designer self._mw.cw_frequency_DoubleSpinBox.setMaximum(constraints.max_frequency) self._mw.cw_frequency_DoubleSpinBox.setMinimum(constraints.min_frequency) self._mw.start_freq_DoubleSpinBox.setMaximum(constraints.max_frequency) self._mw.start_freq_DoubleSpinBox.setMinimum(constraints.min_frequency) self._mw.step_freq_DoubleSpinBox.setMaximum(100e9) self._mw.step_freq_DoubleSpinBox.setOpts(minStep=1.0) # set the minimal step to 1Hz self._mw.stop_freq_DoubleSpinBox.setMaximum(constraints.max_frequency) self._mw.stop_freq_DoubleSpinBox.setMinimum(constraints.min_frequency) self._mw.cw_power_DoubleSpinBox.setMaximum(constraints.max_power) self._mw.cw_power_DoubleSpinBox.setMinimum(constraints.min_power) self._mw.cw_power_DoubleSpinBox.setOpts(minStep=0.1) self._mw.sweep_power_DoubleSpinBox.setMaximum(constraints.max_power) self._mw.sweep_power_DoubleSpinBox.setMinimum(constraints.min_power) self._mw.sweep_power_DoubleSpinBox.setOpts(minStep=0.1) # Add save file tag input box self._mw.save_tag_LineEdit = QtWidgets.QLineEdit(self._mw) self._mw.save_tag_LineEdit.setMaximumWidth(500) self._mw.save_tag_LineEdit.setMinimumWidth(200) self._mw.save_tag_LineEdit.setToolTip('Enter a nametag which will be\n' 'added to the filename.') self._mw.save_ToolBar.addWidget(self._mw.save_tag_LineEdit) # add a clear button to clear the ODMR plots: self._mw.clear_odmr_PushButton = QtWidgets.QPushButton(self._mw) self._mw.clear_odmr_PushButton.setText('Clear ODMR') self._mw.clear_odmr_PushButton.setToolTip('Clear the data of the\n' 'current ODMR measurements.') self._mw.clear_odmr_PushButton.setEnabled(False) self._mw.toolBar.addWidget(self._mw.clear_odmr_PushButton) # Get the image from the logic self.odmr_matrix_image = pg.ImageItem(self._odmr_logic.odmr_plot_xy.transpose()) self.odmr_matrix_image.setRect(QtCore.QRectF( self._odmr_logic.mw_start, 0, self._odmr_logic.mw_stop - self._odmr_logic.mw_start, self._odmr_logic.number_of_lines )) self.odmr_image = pg.PlotDataItem(self._odmr_logic.odmr_plot_x, self._odmr_logic.odmr_plot_y, pen=pg.mkPen(palette.c1, style=QtCore.Qt.DotLine), symbol='o', symbolPen=palette.c1, symbolBrush=palette.c1, symbolSize=7) self.odmr_fit_image = pg.PlotDataItem(self._odmr_logic.odmr_fit_x, self._odmr_logic.odmr_fit_y, pen=pg.mkPen(palette.c2)) # Add the display item to the xy and xz ViewWidget, which was defined in the UI file. self._mw.odmr_PlotWidget.addItem(self.odmr_image) self._mw.odmr_PlotWidget.setLabel(axis='left', text='Counts', units='Counts/s') self._mw.odmr_PlotWidget.setLabel(axis='bottom', text='Frequency', units='Hz') self._mw.odmr_PlotWidget.showGrid(x=True, y=True, alpha=0.8) self._mw.odmr_matrix_PlotWidget.addItem(self.odmr_matrix_image) self._mw.odmr_matrix_PlotWidget.setLabel(axis='left', text='Matrix Lines', units='#') self._mw.odmr_matrix_PlotWidget.setLabel(axis='bottom', text='Frequency', units='Hz') # Get the colorscales at set LUT my_colors = ColorScaleInferno() self.odmr_matrix_image.setLookupTable(my_colors.lut) ######################################################################## # Configuration of the Colorbar # ######################################################################## self.odmr_cb = ColorBar(my_colors.cmap_normed, 100, 0, 100000) # adding colorbar to ViewWidget self._mw.odmr_cb_PlotWidget.addItem(self.odmr_cb) self._mw.odmr_cb_PlotWidget.hideAxis('bottom') self._mw.odmr_cb_PlotWidget.hideAxis('left') self._mw.odmr_cb_PlotWidget.setLabel('right', 'Fluorescence', units='counts/s') ######################################################################## # Configuration of the various display Widgets # ######################################################################## # Take the default values from logic: self._mw.cw_frequency_DoubleSpinBox.setValue(self._odmr_logic.cw_mw_frequency) self._mw.start_freq_DoubleSpinBox.setValue(self._odmr_logic.mw_start) self._mw.stop_freq_DoubleSpinBox.setValue(self._odmr_logic.mw_stop) self._mw.step_freq_DoubleSpinBox.setValue(self._odmr_logic.mw_step) self._mw.cw_power_DoubleSpinBox.setValue(self._odmr_logic.cw_mw_power) self._mw.sweep_power_DoubleSpinBox.setValue(self._odmr_logic.sweep_mw_power) self._mw.runtime_DoubleSpinBox.setValue(self._odmr_logic.run_time) self._mw.elapsed_time_DisplayWidget.display(int(np.rint(self._odmr_logic.elapsed_time))) self._mw.elapsed_sweeps_DisplayWidget.display(self._odmr_logic.elapsed_sweeps) self._sd.matrix_lines_SpinBox.setValue(self._odmr_logic.number_of_lines) self._sd.clock_frequency_DoubleSpinBox.setValue(self._odmr_logic.clock_frequency) # fit settings self._fsd = FitSettingsDialog(self._odmr_logic.fc) self._fsd.sigFitsUpdated.connect(self._mw.fit_methods_ComboBox.setFitFunctions) self._fsd.applySettings() self._mw.action_FitSettings.triggered.connect(self._fsd.show) ######################################################################## # Connect signals # ######################################################################## # Internal user input changed signals self._mw.cw_frequency_DoubleSpinBox.editingFinished.connect(self.change_cw_params) self._mw.start_freq_DoubleSpinBox.editingFinished.connect(self.change_sweep_params) self._mw.step_freq_DoubleSpinBox.editingFinished.connect(self.change_sweep_params) self._mw.stop_freq_DoubleSpinBox.editingFinished.connect(self.change_sweep_params) self._mw.sweep_power_DoubleSpinBox.editingFinished.connect(self.change_sweep_params) self._mw.cw_power_DoubleSpinBox.editingFinished.connect(self.change_cw_params) self._mw.runtime_DoubleSpinBox.editingFinished.connect(self.change_runtime) self._mw.odmr_cb_max_DoubleSpinBox.valueChanged.connect(self.colorscale_changed) self._mw.odmr_cb_min_DoubleSpinBox.valueChanged.connect(self.colorscale_changed) self._mw.odmr_cb_high_percentile_DoubleSpinBox.valueChanged.connect(self.colorscale_changed) self._mw.odmr_cb_low_percentile_DoubleSpinBox.valueChanged.connect(self.colorscale_changed) # Internal trigger signals self._mw.odmr_cb_manual_RadioButton.clicked.connect(self.colorscale_changed) self._mw.odmr_cb_centiles_RadioButton.clicked.connect(self.colorscale_changed) self._mw.clear_odmr_PushButton.clicked.connect(self.clear_odmr_data) self._mw.action_run_stop.triggered.connect(self.run_stop_odmr) self._mw.action_resume_odmr.triggered.connect(self.resume_odmr) self._mw.action_toggle_cw.triggered.connect(self.toggle_cw_mode) self._mw.action_Save.triggered.connect(self.save_data) self._mw.action_RestoreDefault.triggered.connect(self.restore_defaultview) self._mw.do_fit_PushButton.clicked.connect(self.do_fit) # Control/values-changed signals to logic self.sigCwMwOn.connect(self._odmr_logic.mw_cw_on, QtCore.Qt.QueuedConnection) self.sigMwOff.connect(self._odmr_logic.mw_off, QtCore.Qt.QueuedConnection) self.sigClearData.connect(self._odmr_logic.clear_odmr_data, QtCore.Qt.QueuedConnection) self.sigStartOdmrScan.connect(self._odmr_logic.start_odmr_scan, QtCore.Qt.QueuedConnection) self.sigStopOdmrScan.connect(self._odmr_logic.stop_odmr_scan, QtCore.Qt.QueuedConnection) self.sigContinueOdmrScan.connect(self._odmr_logic.continue_odmr_scan, QtCore.Qt.QueuedConnection) self.sigDoFit.connect(self._odmr_logic.do_fit, QtCore.Qt.QueuedConnection) self.sigMwCwParamsChanged.connect(self._odmr_logic.set_cw_parameters, QtCore.Qt.QueuedConnection) self.sigMwSweepParamsChanged.connect(self._odmr_logic.set_sweep_parameters, QtCore.Qt.QueuedConnection) self.sigRuntimeChanged.connect(self._odmr_logic.set_runtime, QtCore.Qt.QueuedConnection) self.sigNumberOfLinesChanged.connect(self._odmr_logic.set_matrix_line_number, QtCore.Qt.QueuedConnection) self.sigSaveMeasurement.connect(self._odmr_logic.save_odmr_data, QtCore.Qt.QueuedConnection) # Update signals coming from logic: self._odmr_logic.sigParameterUpdated.connect(self.update_parameter, QtCore.Qt.QueuedConnection) self._odmr_logic.sigOutputStateUpdated.connect(self.update_status, QtCore.Qt.QueuedConnection) self._odmr_logic.sigOdmrPlotsUpdated.connect(self.update_plots, QtCore.Qt.QueuedConnection) self._odmr_logic.sigOdmrFitUpdated.connect(self.update_fit, QtCore.Qt.QueuedConnection) self._odmr_logic.sigOdmrElapsedTimeUpdated.connect(self.update_elapsedtime, QtCore.Qt.QueuedConnection) # connect settings signals self._mw.action_Settings.triggered.connect(self._menu_settings) self._sd.accepted.connect(self.update_settings) self._sd.rejected.connect(self.reject_settings) self._sd.buttonBox.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect( self.update_settings) self.reject_settings() # Show the Main ODMR GUI: self._show() def on_deactivate(self): """ Reverse steps of activation @return int: error code (0:OK, -1:error) """ # Disconnect signals self._sd.buttonBox.button(QtWidgets.QDialogButtonBox.Apply).clicked.disconnect() self._sd.accepted.disconnect() self._sd.rejected.disconnect() self._mw.action_Settings.triggered.disconnect() self._odmr_logic.sigParameterUpdated.disconnect() self._odmr_logic.sigOutputStateUpdated.disconnect() self._odmr_logic.sigOdmrPlotsUpdated.disconnect() self._odmr_logic.sigOdmrFitUpdated.disconnect() self._odmr_logic.sigOdmrElapsedTimeUpdated.disconnect() self.sigCwMwOn.disconnect() self.sigMwOff.disconnect() self.sigClearData.disconnect() self.sigStartOdmrScan.disconnect() self.sigStopOdmrScan.disconnect() self.sigContinueOdmrScan.disconnect() self.sigDoFit.disconnect() self.sigMwCwParamsChanged.disconnect() self.sigMwSweepParamsChanged.disconnect() self.sigRuntimeChanged.disconnect() self.sigNumberOfLinesChanged.disconnect() self.sigSaveMeasurement.disconnect() self._mw.odmr_cb_manual_RadioButton.clicked.disconnect() self._mw.odmr_cb_centiles_RadioButton.clicked.disconnect() self._mw.clear_odmr_PushButton.clicked.disconnect() self._mw.action_run_stop.triggered.disconnect() self._mw.action_resume_odmr.triggered.disconnect() self._mw.action_Save.triggered.disconnect() self._mw.action_toggle_cw.triggered.disconnect() self._mw.action_RestoreDefault.triggered.disconnect() self._mw.do_fit_PushButton.clicked.disconnect() self._mw.cw_frequency_DoubleSpinBox.editingFinished.disconnect() self._mw.start_freq_DoubleSpinBox.editingFinished.disconnect() self._mw.step_freq_DoubleSpinBox.editingFinished.disconnect() self._mw.stop_freq_DoubleSpinBox.editingFinished.disconnect() self._mw.cw_power_DoubleSpinBox.editingFinished.disconnect() self._mw.sweep_power_DoubleSpinBox.editingFinished.disconnect() self._mw.runtime_DoubleSpinBox.editingFinished.disconnect() self._mw.odmr_cb_max_DoubleSpinBox.valueChanged.disconnect() self._mw.odmr_cb_min_DoubleSpinBox.valueChanged.disconnect() self._mw.odmr_cb_high_percentile_DoubleSpinBox.valueChanged.disconnect() self._mw.odmr_cb_low_percentile_DoubleSpinBox.valueChanged.disconnect() self._fsd.sigFitsUpdated.disconnect() self._mw.action_FitSettings.triggered.disconnect() self._mw.close() return 0 def _show(self): """Make window visible and put it above all other windows. """ self._mw.show() self._mw.activateWindow() self._mw.raise_() return def _menu_settings(self): """ Open the settings menu """ self._sd.exec_() def run_stop_odmr(self, is_checked): """ Manages what happens if odmr scan is started/stopped. """ if is_checked: # change the axes appearance according to input values: self._mw.action_run_stop.setEnabled(False) self._mw.action_resume_odmr.setEnabled(False) self._mw.action_toggle_cw.setEnabled(False) self._mw.odmr_PlotWidget.removeItem(self.odmr_fit_image) self._mw.cw_power_DoubleSpinBox.setEnabled(False) self._mw.sweep_power_DoubleSpinBox.setEnabled(False) self._mw.cw_frequency_DoubleSpinBox.setEnabled(False) self._mw.start_freq_DoubleSpinBox.setEnabled(False) self._mw.step_freq_DoubleSpinBox.setEnabled(False) self._mw.stop_freq_DoubleSpinBox.setEnabled(False) self._mw.runtime_DoubleSpinBox.setEnabled(False) self._sd.clock_frequency_DoubleSpinBox.setEnabled(False) self.sigStartOdmrScan.emit() else: self._mw.action_run_stop.setEnabled(False) self._mw.action_resume_odmr.setEnabled(False) self._mw.action_toggle_cw.setEnabled(False) self.sigStopOdmrScan.emit() return def resume_odmr(self, is_checked): if is_checked: self._mw.action_run_stop.setEnabled(False) self._mw.action_resume_odmr.setEnabled(False) self._mw.action_toggle_cw.setEnabled(False) self._mw.cw_power_DoubleSpinBox.setEnabled(False) self._mw.sweep_power_DoubleSpinBox.setEnabled(False) self._mw.cw_frequency_DoubleSpinBox.setEnabled(False) self._mw.start_freq_DoubleSpinBox.setEnabled(False) self._mw.step_freq_DoubleSpinBox.setEnabled(False) self._mw.stop_freq_DoubleSpinBox.setEnabled(False) self._mw.runtime_DoubleSpinBox.setEnabled(False) self._sd.clock_frequency_DoubleSpinBox.setEnabled(False) self.sigContinueOdmrScan.emit() else: self._mw.action_run_stop.setEnabled(False) self._mw.action_resume_odmr.setEnabled(False) self._mw.action_toggle_cw.setEnabled(False) self.sigStopOdmrScan.emit() return def toggle_cw_mode(self, is_checked): """ Starts or stops CW microwave output if no measurement is running. """ if is_checked: self._mw.action_run_stop.setEnabled(False) self._mw.action_resume_odmr.setEnabled(False) self._mw.action_toggle_cw.setEnabled(False) self._mw.cw_power_DoubleSpinBox.setEnabled(False) self._mw.cw_frequency_DoubleSpinBox.setEnabled(False) self.sigCwMwOn.emit() else: self._mw.action_toggle_cw.setEnabled(False) self.sigMwOff.emit() return def update_status(self, mw_mode, is_running): """ Update the display for a change in the microwave status (mode and output). @param str mw_mode: is the microwave output active? @param bool is_running: is the microwave output active? """ # Block signals from firing self._mw.action_run_stop.blockSignals(True) self._mw.action_resume_odmr.blockSignals(True) self._mw.action_toggle_cw.blockSignals(True) # Update measurement status (activate/deactivate widgets/actions) if is_running: self._mw.action_resume_odmr.setEnabled(False) self._mw.cw_power_DoubleSpinBox.setEnabled(False) self._mw.cw_frequency_DoubleSpinBox.setEnabled(False) if mw_mode != 'cw': self._mw.clear_odmr_PushButton.setEnabled(True) self._mw.action_run_stop.setEnabled(True) self._mw.action_toggle_cw.setEnabled(False) self._mw.start_freq_DoubleSpinBox.setEnabled(False) self._mw.step_freq_DoubleSpinBox.setEnabled(False) self._mw.stop_freq_DoubleSpinBox.setEnabled(False) self._mw.sweep_power_DoubleSpinBox.setEnabled(False) self._mw.runtime_DoubleSpinBox.setEnabled(False) self._sd.clock_frequency_DoubleSpinBox.setEnabled(False) self._mw.action_run_stop.setChecked(True) self._mw.action_resume_odmr.setChecked(True) self._mw.action_toggle_cw.setChecked(False) else: self._mw.clear_odmr_PushButton.setEnabled(False) self._mw.action_run_stop.setEnabled(False) self._mw.action_toggle_cw.setEnabled(True) self._mw.start_freq_DoubleSpinBox.setEnabled(True) self._mw.step_freq_DoubleSpinBox.setEnabled(True) self._mw.stop_freq_DoubleSpinBox.setEnabled(True) self._mw.sweep_power_DoubleSpinBox.setEnabled(True) self._mw.runtime_DoubleSpinBox.setEnabled(True) self._sd.clock_frequency_DoubleSpinBox.setEnabled(True) self._mw.action_run_stop.setChecked(False) self._mw.action_resume_odmr.setChecked(False) self._mw.action_toggle_cw.setChecked(True) else: self._mw.action_resume_odmr.setEnabled(True) self._mw.cw_power_DoubleSpinBox.setEnabled(True) self._mw.sweep_power_DoubleSpinBox.setEnabled(True) self._mw.cw_frequency_DoubleSpinBox.setEnabled(True) self._mw.clear_odmr_PushButton.setEnabled(False) self._mw.action_run_stop.setEnabled(True) self._mw.action_toggle_cw.setEnabled(True) self._mw.start_freq_DoubleSpinBox.setEnabled(True) self._mw.step_freq_DoubleSpinBox.setEnabled(True) self._mw.stop_freq_DoubleSpinBox.setEnabled(True) self._mw.runtime_DoubleSpinBox.setEnabled(True) self._sd.clock_frequency_DoubleSpinBox.setEnabled(True) self._mw.action_run_stop.setChecked(False) self._mw.action_resume_odmr.setChecked(False) self._mw.action_toggle_cw.setChecked(False) # Unblock signal firing self._mw.action_run_stop.blockSignals(False) self._mw.action_resume_odmr.blockSignals(False) self._mw.action_toggle_cw.blockSignals(False) return def clear_odmr_data(self): """ Clear the ODMR data. """ self.sigClearData.emit() return def update_plots(self, odmr_data_x, odmr_data_y, odmr_matrix): """ Refresh the plot widgets with new data. """ # Update mean signal plot self.odmr_image.setData(odmr_data_x, odmr_data_y) # Update raw data matrix plot cb_range = self.get_matrix_cb_range() self.update_colorbar(cb_range) self.odmr_matrix_image.setRect(QtCore.QRectF(odmr_data_x[0], 0, np.abs(odmr_data_x[-1] - odmr_data_x[0]), odmr_matrix.shape[0])) self.odmr_matrix_image.setImage(image=odmr_matrix.transpose(), levels=(cb_range[0], cb_range[1])) return def colorscale_changed(self): """ Updates the range of the displayed colorscale in both the colorbar and the matrix plot. """ cb_range = self.get_matrix_cb_range() self.update_colorbar(cb_range) matrix_image = self.odmr_matrix_image.image self.odmr_matrix_image.setImage(image=matrix_image, levels=(cb_range[0], cb_range[1])) return def update_colorbar(self, cb_range): """ Update the colorbar to a new range. @param list cb_range: List or tuple containing the min and max values for the cb range """ self.odmr_cb.refresh_colorbar(cb_range[0], cb_range[1]) return def get_matrix_cb_range(self): """ Determines the cb_min and cb_max values for the matrix plot """ matrix_image = self.odmr_matrix_image.image # If "Manual" is checked or the image is empty (all zeros), then take manual cb range. # Otherwise, calculate cb range from percentiles. if self._mw.odmr_cb_manual_RadioButton.isChecked() or np.max(matrix_image) < 0.1: cb_min = self._mw.odmr_cb_min_DoubleSpinBox.value() cb_max = self._mw.odmr_cb_max_DoubleSpinBox.value() else: # Exclude any zeros (which are typically due to unfinished scan) matrix_image_nonzero = matrix_image[np.nonzero(matrix_image)] # Read centile range low_centile = self._mw.odmr_cb_low_percentile_DoubleSpinBox.value() high_centile = self._mw.odmr_cb_high_percentile_DoubleSpinBox.value() cb_min = np.percentile(matrix_image_nonzero, low_centile) cb_max = np.percentile(matrix_image_nonzero, high_centile) cb_range = [cb_min, cb_max] return cb_range def restore_defaultview(self): self._mw.restoreGeometry(self.mwsettings.value("geometry", "")) self._mw.restoreState(self.mwsettings.value("windowState", "")) def update_elapsedtime(self, elapsed_time, scanned_lines): """ Updates current elapsed measurement time and completed frequency sweeps """ self._mw.elapsed_time_DisplayWidget.display(int(np.rint(elapsed_time))) self._mw.elapsed_sweeps_DisplayWidget.display(scanned_lines) return def update_settings(self): """ Write the new settings from the gui to the file. """ number_of_lines = self._sd.matrix_lines_SpinBox.value() clock_frequency = self._sd.clock_frequency_DoubleSpinBox.value() self.sigClockFreqChanged.emit(clock_frequency) self.sigNumberOfLinesChanged.emit(number_of_lines) return def reject_settings(self): """ Keep the old settings and restores the old settings in the gui. """ self._sd.matrix_lines_SpinBox.setValue(self._odmr_logic.number_of_lines) self._sd.clock_frequency_DoubleSpinBox.setValue(self._odmr_logic.clock_frequency) return def do_fit(self): fit_function = self._mw.fit_methods_ComboBox.getCurrentFit()[0] self.sigDoFit.emit(fit_function) return def update_fit(self, x_data, y_data, result_str_dict, current_fit): """ Update the shown fit. """ if current_fit != 'No Fit': # display results as formatted text self._mw.odmr_fit_results_DisplayWidget.clear() try: formated_results = units.create_formatted_output(result_str_dict) except: formated_results = 'this fit does not return formatted results' self._mw.odmr_fit_results_DisplayWidget.setPlainText(formated_results) self._mw.fit_methods_ComboBox.blockSignals(True) self._mw.fit_methods_ComboBox.setCurrentFit(current_fit) self._mw.fit_methods_ComboBox.blockSignals(False) # check which Fit method is used and remove or add again the # odmr_fit_image, check also whether a odmr_fit_image already exists. if current_fit != 'No Fit': self.odmr_fit_image.setData(x=x_data, y=y_data) if self.odmr_fit_image not in self._mw.odmr_PlotWidget.listDataItems(): self._mw.odmr_PlotWidget.addItem(self.odmr_fit_image) else: if self.odmr_fit_image in self._mw.odmr_PlotWidget.listDataItems(): self._mw.odmr_PlotWidget.removeItem(self.odmr_fit_image) self._mw.odmr_PlotWidget.getViewBox().updateAutoRange() return def update_parameter(self, param_dict): """ Update the parameter display in the GUI. @param param_dict: @return: Any change event from the logic should call this update function. The update will block the GUI signals from emitting a change back to the logic. """ param = param_dict.get('mw_power') if param is not None: self._mw.power_DoubleSpinBox.blockSignals(True) self._mw.power_DoubleSpinBox.setValue(param) self._mw.power_DoubleSpinBox.blockSignals(False) param = param_dict.get('mw_frequency') if param is not None: self._mw.frequency_DoubleSpinBox.blockSignals(True) self._mw.frequency_DoubleSpinBox.setValue(param) self._mw.frequency_DoubleSpinBox.blockSignals(False) param = param_dict.get('mw_start') if param is not None: self._mw.start_freq_DoubleSpinBox.blockSignals(True) self._mw.start_freq_DoubleSpinBox.setValue(param) self._mw.start_freq_DoubleSpinBox.blockSignals(False) param = param_dict.get('mw_step') if param is not None: self._mw.step_freq_DoubleSpinBox.blockSignals(True) self._mw.step_freq_DoubleSpinBox.setValue(param) self._mw.step_freq_DoubleSpinBox.blockSignals(False) param = param_dict.get('mw_stop') if param is not None: self._mw.stop_freq_DoubleSpinBox.blockSignals(True) self._mw.stop_freq_DoubleSpinBox.setValue(param) self._mw.stop_freq_DoubleSpinBox.blockSignals(False) param = param_dict.get('run_time') if param is not None: self._mw.runtime_DoubleSpinBox.blockSignals(True) self._mw.runtime_DoubleSpinBox.setValue(param) self._mw.runtime_DoubleSpinBox.blockSignals(False) param = param_dict.get('number_of_lines') if param is not None: self._sd.matrix_lines_SpinBox.blockSignals(True) self._sd.matrix_lines_SpinBox.setValue(param) self._sd.matrix_lines_SpinBox.blockSignals(False) param = param_dict.get('clock_frequency') if param is not None: self._sd.clock_frequency_DoubleSpinBox.blockSignals(True) self._sd.clock_frequency_DoubleSpinBox.setValue(param) self._sd.clock_frequency_DoubleSpinBox.blockSignals(False) return ############################################################################ # Change Methods # ############################################################################ def change_cw_params(self): """ Change CW frequency and power of microwave source """ frequency = self._mw.cw_frequency_DoubleSpinBox.value() power = self._mw.cw_power_DoubleSpinBox.value() self.sigMwCwParamsChanged.emit(frequency, power) return def change_sweep_params(self): """ Change start, stop and step frequency of frequency sweep """ start = self._mw.start_freq_DoubleSpinBox.value() stop = self._mw.stop_freq_DoubleSpinBox.value() step = self._mw.step_freq_DoubleSpinBox.value() power = self._mw.sweep_power_DoubleSpinBox.value() self.sigMwSweepParamsChanged.emit(start, stop, step, power) return def change_runtime(self): """ Change time after which microwave sweep is stopped """ runtime = self._mw.runtime_DoubleSpinBox.value() self.sigRuntimeChanged.emit(runtime) return def save_data(self): """ Save the sum plot, the scan marix plot and the scan data """ filetag = self._mw.save_tag_LineEdit.text() cb_range = self.get_matrix_cb_range() # Percentile range is None, unless the percentile scaling is selected in GUI. pcile_range = None if self._mw.odmr_cb_centiles_RadioButton.isChecked(): low_centile = self._mw.odmr_cb_low_percentile_DoubleSpinBox.value() high_centile = self._mw.odmr_cb_high_percentile_DoubleSpinBox.value() pcile_range = [low_centile, high_centile] self.sigSaveMeasurement.emit(filetag, cb_range, pcile_range) return
class ODMRGui(GUIBase): """ This is the GUI Class for ODMR measurements """ # declare connectors odmrlogic1 = Connector(interface='ODMRAWGLogic') savelogic = Connector(interface='SaveLogic') sigStartOdmrScan = QtCore.Signal() sigStopOdmrScan = QtCore.Signal() sigContinueOdmrScan = QtCore.Signal() sigClearData = QtCore.Signal() sigCwMwOn = QtCore.Signal() sigMwOff = QtCore.Signal() sigMwPowerChanged = QtCore.Signal(float) sigMwCwParamsChanged = QtCore.Signal(float, float) sigMwSweepParamsChanged = QtCore.Signal(float, float, float, float) sigClockFreqChanged = QtCore.Signal(float) sigOversamplingChanged = QtCore.Signal(int) sigLockInChanged = QtCore.Signal(bool) sigFitChanged = QtCore.Signal(str) sigNumberOfLinesChanged = QtCore.Signal(int) sigRuntimeChanged = QtCore.Signal(float) sigDoFit = QtCore.Signal(str, object, object, int) sigSaveMeasurement = QtCore.Signal(str, list, list) sigAverageLinesChanged = QtCore.Signal(int) def __init__(self, config, **kwargs): super().__init__(config=config, **kwargs) def on_activate(self): """ Definition, configuration and initialisation of the ODMR GUI. This init connects all the graphic modules, which were created in the *.ui file and configures the event handling between the modules. """ self._odmr_logic = self.odmrlogic1() # Use the inherited class 'Ui_ODMRGuiUI' to create now the GUI element: self._mw = ODMRMainWindow() self._sd = ODMRSettingDialog() # Create a QSettings object for the mainwindow and store the actual GUI layout self.mwsettings = QtCore.QSettings("QUDI", "ODMR") self.mwsettings.setValue("geometry", self._mw.saveGeometry()) self.mwsettings.setValue("windowState", self._mw.saveState()) # Get hardware constraints to set limits for input widgets constraints = self._odmr_logic.get_hw_constraints() # Adjust range of scientific spinboxes above what is possible in Qt Designer self._mw.cw_frequency_DoubleSpinBox.setMaximum( constraints.max_frequency) self._mw.cw_frequency_DoubleSpinBox.setMinimum( constraints.min_frequency) self._mw.start_freq_DoubleSpinBox.setMaximum(constraints.max_frequency) self._mw.start_freq_DoubleSpinBox.setMinimum(constraints.min_frequency) self._mw.step_freq_DoubleSpinBox.setMaximum(100e9) self._mw.stop_freq_DoubleSpinBox.setMaximum(constraints.max_frequency) self._mw.stop_freq_DoubleSpinBox.setMinimum(constraints.min_frequency) self._mw.cw_power_DoubleSpinBox.setMaximum(constraints.max_power) self._mw.cw_power_DoubleSpinBox.setMinimum(constraints.min_power) self._mw.sweep_power_DoubleSpinBox.setMaximum(constraints.max_power) self._mw.sweep_power_DoubleSpinBox.setMinimum(constraints.min_power) # Add save file tag input box self._mw.save_tag_LineEdit = QtWidgets.QLineEdit(self._mw) self._mw.save_tag_LineEdit.setMaximumWidth(500) self._mw.save_tag_LineEdit.setMinimumWidth(200) self._mw.save_tag_LineEdit.setToolTip('Enter a nametag which will be\n' 'added to the filename.') self._mw.save_ToolBar.addWidget(self._mw.save_tag_LineEdit) # add a clear button to clear the ODMR plots: self._mw.clear_odmr_PushButton = QtWidgets.QPushButton(self._mw) self._mw.clear_odmr_PushButton.setText('Clear ODMR') self._mw.clear_odmr_PushButton.setToolTip('Clear the data of the\n' 'current ODMR measurements.') self._mw.clear_odmr_PushButton.setEnabled(False) self._mw.toolBar.addWidget(self._mw.clear_odmr_PushButton) # Set up and connect channel combobox self.display_channel = 0 odmr_channels = self._odmr_logic.get_odmr_channels() for n, ch in enumerate(odmr_channels): self._mw.odmr_channel_ComboBox.addItem(str(ch), n) self._mw.odmr_channel_ComboBox.activated.connect(self.update_channel) # Get the image from the logic self.odmr_matrix_image = pg.ImageItem( self._odmr_logic.odmr_plot_xy[:, self.display_channel], axisOrder='row-major') self.odmr_matrix_image.setRect( QtCore.QRectF(self._odmr_logic.mw_start, 0, self._odmr_logic.mw_stop - self._odmr_logic.mw_start, self._odmr_logic.number_of_lines)) self.odmr_image = pg.PlotDataItem( self._odmr_logic.odmr_plot_x, self._odmr_logic.odmr_plot_y[self.display_channel], pen=pg.mkPen(palette.c1, style=QtCore.Qt.DotLine), symbol='o', symbolPen=palette.c1, symbolBrush=palette.c1, symbolSize=7) self.odmr_fit_image = pg.PlotDataItem(self._odmr_logic.odmr_fit_x, self._odmr_logic.odmr_fit_y, pen=pg.mkPen(palette.c2)) # Add the display item to the xy and xz ViewWidget, which was defined in the UI file. self._mw.odmr_PlotWidget.addItem(self.odmr_image) self._mw.odmr_PlotWidget.setLabel(axis='left', text='Counts', units='Counts/s') self._mw.odmr_PlotWidget.setLabel(axis='bottom', text='Frequency', units='Hz') self._mw.odmr_PlotWidget.showGrid(x=True, y=True, alpha=0.8) self._mw.odmr_matrix_PlotWidget.addItem(self.odmr_matrix_image) self._mw.odmr_matrix_PlotWidget.setLabel(axis='left', text='Matrix Lines', units='#') self._mw.odmr_matrix_PlotWidget.setLabel(axis='bottom', text='Frequency', units='Hz') # Get the colorscales at set LUT my_colors = ColorScaleInferno() self.odmr_matrix_image.setLookupTable(my_colors.lut) ######################################################################## # Configuration of the Colorbar # ######################################################################## self.odmr_cb = ColorBar(my_colors.cmap_normed, 100, 0, 100000) # adding colorbar to ViewWidget self._mw.odmr_cb_PlotWidget.addItem(self.odmr_cb) self._mw.odmr_cb_PlotWidget.hideAxis('bottom') self._mw.odmr_cb_PlotWidget.hideAxis('left') self._mw.odmr_cb_PlotWidget.setLabel('right', 'Fluorescence', units='counts/s') ######################################################################## # Configuration of the various display Widgets # ######################################################################## # Take the default values from logic: self._mw.cw_frequency_DoubleSpinBox.setValue( self._odmr_logic.cw_mw_frequency) self._mw.start_freq_DoubleSpinBox.setValue(self._odmr_logic.mw_start) self._mw.stop_freq_DoubleSpinBox.setValue(self._odmr_logic.mw_stop) self._mw.step_freq_DoubleSpinBox.setValue(self._odmr_logic.mw_step) self._mw.cw_power_DoubleSpinBox.setValue(self._odmr_logic.cw_mw_power) self._mw.sweep_power_DoubleSpinBox.setValue( self._odmr_logic.sweep_mw_power) self._mw.runtime_DoubleSpinBox.setValue(self._odmr_logic.run_time) self._mw.elapsed_time_DisplayWidget.display( int(np.rint(self._odmr_logic.elapsed_time))) self._mw.elapsed_sweeps_DisplayWidget.display( self._odmr_logic.elapsed_sweeps) self._mw.average_level_SpinBox.setValue( self._odmr_logic.lines_to_average) self._sd.matrix_lines_SpinBox.setValue( self._odmr_logic.number_of_lines) self._sd.clock_frequency_DoubleSpinBox.setValue( self._odmr_logic.clock_frequency) self._sd.oversampling_SpinBox.setValue(self._odmr_logic.oversampling) self._sd.lock_in_CheckBox.setChecked(self._odmr_logic.lock_in) # fit settings self._fsd = FitSettingsDialog(self._odmr_logic.fc) self._fsd.sigFitsUpdated.connect( self._mw.fit_methods_ComboBox.setFitFunctions) self._fsd.applySettings() self._mw.action_FitSettings.triggered.connect(self._fsd.show) ######################################################################## # Connect signals # ######################################################################## # Internal user input changed signals self._mw.cw_frequency_DoubleSpinBox.editingFinished.connect( self.change_cw_params) self._mw.start_freq_DoubleSpinBox.editingFinished.connect( self.change_sweep_params) self._mw.step_freq_DoubleSpinBox.editingFinished.connect( self.change_sweep_params) self._mw.stop_freq_DoubleSpinBox.editingFinished.connect( self.change_sweep_params) self._mw.sweep_power_DoubleSpinBox.editingFinished.connect( self.change_sweep_params) self._mw.cw_power_DoubleSpinBox.editingFinished.connect( self.change_cw_params) self._mw.runtime_DoubleSpinBox.editingFinished.connect( self.change_runtime) self._mw.odmr_cb_max_DoubleSpinBox.valueChanged.connect( self.colorscale_changed) self._mw.odmr_cb_min_DoubleSpinBox.valueChanged.connect( self.colorscale_changed) self._mw.odmr_cb_high_percentile_DoubleSpinBox.valueChanged.connect( self.colorscale_changed) self._mw.odmr_cb_low_percentile_DoubleSpinBox.valueChanged.connect( self.colorscale_changed) self._mw.average_level_SpinBox.valueChanged.connect( self.average_level_changed) # Internal trigger signals self._mw.odmr_cb_manual_RadioButton.clicked.connect( self.colorscale_changed) self._mw.odmr_cb_centiles_RadioButton.clicked.connect( self.colorscale_changed) self._mw.clear_odmr_PushButton.clicked.connect(self.clear_odmr_data) self._mw.action_run_stop.triggered.connect(self.run_stop_odmr) self._mw.action_resume_odmr.triggered.connect(self.resume_odmr) self._mw.action_toggle_cw.triggered.connect(self.toggle_cw_mode) self._mw.action_Save.triggered.connect(self.save_data) self._mw.action_RestoreDefault.triggered.connect( self.restore_defaultview) self._mw.do_fit_PushButton.clicked.connect(self.do_fit) # Control/values-changed signals to logic self.sigCwMwOn.connect(self._odmr_logic.mw_cw_on, QtCore.Qt.QueuedConnection) self.sigMwOff.connect(self._odmr_logic.mw_off, QtCore.Qt.QueuedConnection) self.sigClearData.connect(self._odmr_logic.clear_odmr_data, QtCore.Qt.QueuedConnection) self.sigStartOdmrScan.connect(self._odmr_logic.start_odmr_scan, QtCore.Qt.QueuedConnection) self.sigStopOdmrScan.connect(self._odmr_logic.stop_odmr_scan, QtCore.Qt.QueuedConnection) self.sigContinueOdmrScan.connect(self._odmr_logic.continue_odmr_scan, QtCore.Qt.QueuedConnection) self.sigDoFit.connect(self._odmr_logic.do_fit, QtCore.Qt.QueuedConnection) self.sigMwCwParamsChanged.connect(self._odmr_logic.set_cw_parameters, QtCore.Qt.QueuedConnection) self.sigMwSweepParamsChanged.connect( self._odmr_logic.set_sweep_parameters, QtCore.Qt.QueuedConnection) self.sigRuntimeChanged.connect(self._odmr_logic.set_runtime, QtCore.Qt.QueuedConnection) self.sigNumberOfLinesChanged.connect( self._odmr_logic.set_matrix_line_number, QtCore.Qt.QueuedConnection) self.sigClockFreqChanged.connect(self._odmr_logic.set_clock_frequency, QtCore.Qt.QueuedConnection) self.sigOversamplingChanged.connect(self._odmr_logic.set_oversampling, QtCore.Qt.QueuedConnection) self.sigLockInChanged.connect(self._odmr_logic.set_lock_in, QtCore.Qt.QueuedConnection) self.sigSaveMeasurement.connect(self._odmr_logic.save_odmr_data, QtCore.Qt.QueuedConnection) self.sigAverageLinesChanged.connect( self._odmr_logic.set_average_length, QtCore.Qt.QueuedConnection) # Update signals coming from logic: self._odmr_logic.sigParameterUpdated.connect( self.update_parameter, QtCore.Qt.QueuedConnection) self._odmr_logic.sigOutputStateUpdated.connect( self.update_status, QtCore.Qt.QueuedConnection) self._odmr_logic.sigOdmrPlotsUpdated.connect( self.update_plots, QtCore.Qt.QueuedConnection) self._odmr_logic.sigOdmrFitUpdated.connect(self.update_fit, QtCore.Qt.QueuedConnection) self._odmr_logic.sigOdmrElapsedTimeUpdated.connect( self.update_elapsedtime, QtCore.Qt.QueuedConnection) # connect settings signals self._mw.action_Settings.triggered.connect(self._menu_settings) self._sd.accepted.connect(self.update_settings) self._sd.rejected.connect(self.reject_settings) self._sd.buttonBox.button( QtWidgets.QDialogButtonBox.Apply).clicked.connect( self.update_settings) self.reject_settings() # Show the Main ODMR GUI: self.show() def on_deactivate(self): """ Reverse steps of activation @return int: error code (0:OK, -1:error) """ # Disconnect signals self._sd.buttonBox.button( QtWidgets.QDialogButtonBox.Apply).clicked.disconnect() self._sd.accepted.disconnect() self._sd.rejected.disconnect() self._mw.action_Settings.triggered.disconnect() self._odmr_logic.sigParameterUpdated.disconnect() self._odmr_logic.sigOutputStateUpdated.disconnect() self._odmr_logic.sigOdmrPlotsUpdated.disconnect() self._odmr_logic.sigOdmrFitUpdated.disconnect() self._odmr_logic.sigOdmrElapsedTimeUpdated.disconnect() self.sigCwMwOn.disconnect() self.sigMwOff.disconnect() self.sigClearData.disconnect() self.sigStartOdmrScan.disconnect() self.sigStopOdmrScan.disconnect() self.sigContinueOdmrScan.disconnect() self.sigDoFit.disconnect() self.sigMwCwParamsChanged.disconnect() self.sigMwSweepParamsChanged.disconnect() self.sigRuntimeChanged.disconnect() self.sigNumberOfLinesChanged.disconnect() self.sigClockFreqChanged.disconnect() self.sigOversamplingChanged.disconnect() self.sigLockInChanged.disconnect() self.sigSaveMeasurement.disconnect() self.sigAverageLinesChanged.disconnect() self._mw.odmr_cb_manual_RadioButton.clicked.disconnect() self._mw.odmr_cb_centiles_RadioButton.clicked.disconnect() self._mw.clear_odmr_PushButton.clicked.disconnect() self._mw.action_run_stop.triggered.disconnect() self._mw.action_resume_odmr.triggered.disconnect() self._mw.action_Save.triggered.disconnect() self._mw.action_toggle_cw.triggered.disconnect() self._mw.action_RestoreDefault.triggered.disconnect() self._mw.do_fit_PushButton.clicked.disconnect() self._mw.cw_frequency_DoubleSpinBox.editingFinished.disconnect() self._mw.start_freq_DoubleSpinBox.editingFinished.disconnect() self._mw.step_freq_DoubleSpinBox.editingFinished.disconnect() self._mw.stop_freq_DoubleSpinBox.editingFinished.disconnect() self._mw.cw_power_DoubleSpinBox.editingFinished.disconnect() self._mw.sweep_power_DoubleSpinBox.editingFinished.disconnect() self._mw.runtime_DoubleSpinBox.editingFinished.disconnect() self._mw.odmr_cb_max_DoubleSpinBox.valueChanged.disconnect() self._mw.odmr_cb_min_DoubleSpinBox.valueChanged.disconnect() self._mw.odmr_cb_high_percentile_DoubleSpinBox.valueChanged.disconnect( ) self._mw.odmr_cb_low_percentile_DoubleSpinBox.valueChanged.disconnect() self._mw.average_level_SpinBox.valueChanged.disconnect() self._fsd.sigFitsUpdated.disconnect() self._mw.action_FitSettings.triggered.disconnect() self._mw.close() return 0 def show(self): """Make window visible and put it above all other windows. """ self._mw.show() self._mw.activateWindow() self._mw.raise_() def _menu_settings(self): """ Open the settings menu """ self._sd.exec_() def run_stop_odmr(self, is_checked): """ Manages what happens if odmr scan is started/stopped. """ if is_checked: # change the axes appearance according to input values: self._mw.action_run_stop.setEnabled(False) self._mw.action_resume_odmr.setEnabled(False) self._mw.action_toggle_cw.setEnabled(False) self._mw.odmr_PlotWidget.removeItem(self.odmr_fit_image) self._mw.cw_power_DoubleSpinBox.setEnabled(False) self._mw.sweep_power_DoubleSpinBox.setEnabled(False) self._mw.cw_frequency_DoubleSpinBox.setEnabled(False) self._mw.start_freq_DoubleSpinBox.setEnabled(False) self._mw.step_freq_DoubleSpinBox.setEnabled(False) self._mw.stop_freq_DoubleSpinBox.setEnabled(False) self._mw.runtime_DoubleSpinBox.setEnabled(False) self._sd.clock_frequency_DoubleSpinBox.setEnabled(False) self._sd.oversampling_SpinBox.setEnabled(False) self._sd.lock_in_CheckBox.setEnabled(False) self.sigStartOdmrScan.emit() else: self._mw.action_run_stop.setEnabled(False) self._mw.action_resume_odmr.setEnabled(False) self._mw.action_toggle_cw.setEnabled(False) self.sigStopOdmrScan.emit() return def resume_odmr(self, is_checked): if is_checked: self._mw.action_run_stop.setEnabled(False) self._mw.action_resume_odmr.setEnabled(False) self._mw.action_toggle_cw.setEnabled(False) self._mw.cw_power_DoubleSpinBox.setEnabled(False) self._mw.sweep_power_DoubleSpinBox.setEnabled(False) self._mw.cw_frequency_DoubleSpinBox.setEnabled(False) self._mw.start_freq_DoubleSpinBox.setEnabled(False) self._mw.step_freq_DoubleSpinBox.setEnabled(False) self._mw.stop_freq_DoubleSpinBox.setEnabled(False) self._mw.runtime_DoubleSpinBox.setEnabled(False) self._sd.clock_frequency_DoubleSpinBox.setEnabled(False) self._sd.oversampling_SpinBox.setEnabled(False) self._sd.lock_in_CheckBox.setEnabled(False) self.sigContinueOdmrScan.emit() else: self._mw.action_run_stop.setEnabled(False) self._mw.action_resume_odmr.setEnabled(False) self._mw.action_toggle_cw.setEnabled(False) self.sigStopOdmrScan.emit() return def toggle_cw_mode(self, is_checked): """ Starts or stops CW microwave output if no measurement is running. """ if is_checked: self._mw.action_run_stop.setEnabled(False) self._mw.action_resume_odmr.setEnabled(False) self._mw.action_toggle_cw.setEnabled(False) self._mw.cw_power_DoubleSpinBox.setEnabled(False) self._mw.cw_frequency_DoubleSpinBox.setEnabled(False) self.sigCwMwOn.emit() else: self._mw.action_toggle_cw.setEnabled(False) self.sigMwOff.emit() return def update_status(self, mw_mode, is_running): """ Update the display for a change in the microwave status (mode and output). @param str mw_mode: is the microwave output active? @param bool is_running: is the microwave output active? """ # Block signals from firing self._mw.action_run_stop.blockSignals(True) self._mw.action_resume_odmr.blockSignals(True) self._mw.action_toggle_cw.blockSignals(True) # Update measurement status (activate/deactivate widgets/actions) if is_running: self._mw.action_resume_odmr.setEnabled(False) self._mw.cw_power_DoubleSpinBox.setEnabled(False) self._mw.cw_frequency_DoubleSpinBox.setEnabled(False) if mw_mode != 'cw': self._mw.clear_odmr_PushButton.setEnabled(True) self._mw.action_run_stop.setEnabled(True) self._mw.action_toggle_cw.setEnabled(False) self._mw.start_freq_DoubleSpinBox.setEnabled(False) self._mw.step_freq_DoubleSpinBox.setEnabled(False) self._mw.stop_freq_DoubleSpinBox.setEnabled(False) self._mw.sweep_power_DoubleSpinBox.setEnabled(False) self._mw.runtime_DoubleSpinBox.setEnabled(False) self._sd.clock_frequency_DoubleSpinBox.setEnabled(False) self._sd.oversampling_SpinBox.setEnabled(False) self._sd.lock_in_CheckBox.setEnabled(False) self._mw.action_run_stop.setChecked(True) self._mw.action_resume_odmr.setChecked(True) self._mw.action_toggle_cw.setChecked(False) else: self._mw.clear_odmr_PushButton.setEnabled(False) self._mw.action_run_stop.setEnabled(False) self._mw.action_toggle_cw.setEnabled(True) self._mw.start_freq_DoubleSpinBox.setEnabled(True) self._mw.step_freq_DoubleSpinBox.setEnabled(True) self._mw.stop_freq_DoubleSpinBox.setEnabled(True) self._mw.sweep_power_DoubleSpinBox.setEnabled(True) self._mw.runtime_DoubleSpinBox.setEnabled(True) self._sd.clock_frequency_DoubleSpinBox.setEnabled(True) self._sd.oversampling_SpinBox.setEnabled(True) self._sd.lock_in_CheckBox.setEnabled(True) self._mw.action_run_stop.setChecked(False) self._mw.action_resume_odmr.setChecked(False) self._mw.action_toggle_cw.setChecked(True) else: self._mw.action_resume_odmr.setEnabled(True) self._mw.cw_power_DoubleSpinBox.setEnabled(True) self._mw.sweep_power_DoubleSpinBox.setEnabled(True) self._mw.cw_frequency_DoubleSpinBox.setEnabled(True) self._mw.clear_odmr_PushButton.setEnabled(False) self._mw.action_run_stop.setEnabled(True) self._mw.action_toggle_cw.setEnabled(True) self._mw.start_freq_DoubleSpinBox.setEnabled(True) self._mw.step_freq_DoubleSpinBox.setEnabled(True) self._mw.stop_freq_DoubleSpinBox.setEnabled(True) self._mw.runtime_DoubleSpinBox.setEnabled(True) self._sd.clock_frequency_DoubleSpinBox.setEnabled(True) self._sd.oversampling_SpinBox.setEnabled(True) self._sd.lock_in_CheckBox.setEnabled(True) self._mw.action_run_stop.setChecked(False) self._mw.action_resume_odmr.setChecked(False) self._mw.action_toggle_cw.setChecked(False) # Unblock signal firing self._mw.action_run_stop.blockSignals(False) self._mw.action_resume_odmr.blockSignals(False) self._mw.action_toggle_cw.blockSignals(False) return def clear_odmr_data(self): """ Clear the ODMR data. """ self.sigClearData.emit() return def update_plots(self, odmr_data_x, odmr_data_y, odmr_matrix): """ Refresh the plot widgets with new data. """ # Update mean signal plot self.odmr_image.setData(odmr_data_x, odmr_data_y[self.display_channel]) # Update raw data matrix plot cb_range = self.get_matrix_cb_range() self.update_colorbar(cb_range) self.odmr_matrix_image.setRect( QtCore.QRectF(odmr_data_x[0], 0, np.abs(odmr_data_x[-1] - odmr_data_x[0]), odmr_matrix.shape[0])) self.odmr_matrix_image.setImage( image=odmr_matrix[:, self.display_channel], axisOrder='row-major', levels=(cb_range[0], cb_range[1])) def update_channel(self, index): self.display_channel = int( self._mw.odmr_channel_ComboBox.itemData(index, QtCore.Qt.UserRole)) self.update_plots(self._odmr_logic.odmr_plot_x, self._odmr_logic.odmr_plot_y, self._odmr_logic.odmr_plot_xy) def average_level_changed(self): """ Sends to lines to average to the logic """ self.sigAverageLinesChanged.emit( self._mw.average_level_SpinBox.value()) return def colorscale_changed(self): """ Updates the range of the displayed colorscale in both the colorbar and the matrix plot. """ cb_range = self.get_matrix_cb_range() self.update_colorbar(cb_range) matrix_image = self.odmr_matrix_image.image self.odmr_matrix_image.setImage(image=matrix_image, levels=(cb_range[0], cb_range[1])) return def update_colorbar(self, cb_range): """ Update the colorbar to a new range. @param list cb_range: List or tuple containing the min and max values for the cb range """ self.odmr_cb.refresh_colorbar(cb_range[0], cb_range[1]) return def get_matrix_cb_range(self): """ Determines the cb_min and cb_max values for the matrix plot """ matrix_image = self.odmr_matrix_image.image # If "Manual" is checked or the image is empty (all zeros), then take manual cb range. # Otherwise, calculate cb range from percentiles. if self._mw.odmr_cb_manual_RadioButton.isChecked( ) or np.count_nonzero(matrix_image) < 1: cb_min = self._mw.odmr_cb_min_DoubleSpinBox.value() cb_max = self._mw.odmr_cb_max_DoubleSpinBox.value() else: # Exclude any zeros (which are typically due to unfinished scan) matrix_image_nonzero = matrix_image[np.nonzero(matrix_image)] # Read centile range low_centile = self._mw.odmr_cb_low_percentile_DoubleSpinBox.value() high_centile = self._mw.odmr_cb_high_percentile_DoubleSpinBox.value( ) cb_min = np.percentile(matrix_image_nonzero, low_centile) cb_max = np.percentile(matrix_image_nonzero, high_centile) cb_range = [cb_min, cb_max] return cb_range def restore_defaultview(self): self._mw.restoreGeometry(self.mwsettings.value("geometry", "")) self._mw.restoreState(self.mwsettings.value("windowState", "")) def update_elapsedtime(self, elapsed_time, scanned_lines): """ Updates current elapsed measurement time and completed frequency sweeps """ self._mw.elapsed_time_DisplayWidget.display(int(np.rint(elapsed_time))) self._mw.elapsed_sweeps_DisplayWidget.display(scanned_lines) return def update_settings(self): """ Write the new settings from the gui to the file. """ number_of_lines = self._sd.matrix_lines_SpinBox.value() clock_frequency = self._sd.clock_frequency_DoubleSpinBox.value() oversampling = self._sd.oversampling_SpinBox.value() lock_in = self._sd.lock_in_CheckBox.isChecked() self.sigOversamplingChanged.emit(oversampling) self.sigLockInChanged.emit(lock_in) self.sigClockFreqChanged.emit(clock_frequency) self.sigNumberOfLinesChanged.emit(number_of_lines) return def reject_settings(self): """ Keep the old settings and restores the old settings in the gui. """ self._sd.matrix_lines_SpinBox.setValue( self._odmr_logic.number_of_lines) self._sd.clock_frequency_DoubleSpinBox.setValue( self._odmr_logic.clock_frequency) self._sd.oversampling_SpinBox.setValue(self._odmr_logic.oversampling) self._sd.lock_in_CheckBox.setChecked(self._odmr_logic.lock_in) return def do_fit(self): fit_function = self._mw.fit_methods_ComboBox.getCurrentFit()[0] self.sigDoFit.emit(fit_function, None, None, self._mw.odmr_channel_ComboBox.currentIndex()) return def update_fit(self, x_data, y_data, result_str_dict, current_fit): """ Update the shown fit. """ if current_fit != 'No Fit': # display results as formatted text self._mw.odmr_fit_results_DisplayWidget.clear() try: formated_results = units.create_formatted_output( result_str_dict) except: formated_results = 'this fit does not return formatted results' self._mw.odmr_fit_results_DisplayWidget.setPlainText( formated_results) self._mw.fit_methods_ComboBox.blockSignals(True) self._mw.fit_methods_ComboBox.setCurrentFit(current_fit) self._mw.fit_methods_ComboBox.blockSignals(False) # check which Fit method is used and remove or add again the # odmr_fit_image, check also whether a odmr_fit_image already exists. if current_fit != 'No Fit': self.odmr_fit_image.setData(x=x_data, y=y_data) if self.odmr_fit_image not in self._mw.odmr_PlotWidget.listDataItems( ): self._mw.odmr_PlotWidget.addItem(self.odmr_fit_image) else: if self.odmr_fit_image in self._mw.odmr_PlotWidget.listDataItems(): self._mw.odmr_PlotWidget.removeItem(self.odmr_fit_image) self._mw.odmr_PlotWidget.getViewBox().updateAutoRange() return def update_parameter(self, param_dict): """ Update the parameter display in the GUI. @param param_dict: @return: Any change event from the logic should call this update function. The update will block the GUI signals from emitting a change back to the logic. """ param = param_dict.get('sweep_mw_power') if param is not None: self._mw.sweep_power_DoubleSpinBox.blockSignals(True) self._mw.sweep_power_DoubleSpinBox.setValue(param) self._mw.sweep_power_DoubleSpinBox.blockSignals(False) param = param_dict.get('mw_start') if param is not None: self._mw.start_freq_DoubleSpinBox.blockSignals(True) self._mw.start_freq_DoubleSpinBox.setValue(param) self._mw.start_freq_DoubleSpinBox.blockSignals(False) param = param_dict.get('mw_step') if param is not None: self._mw.step_freq_DoubleSpinBox.blockSignals(True) self._mw.step_freq_DoubleSpinBox.setValue(param) self._mw.step_freq_DoubleSpinBox.blockSignals(False) param = param_dict.get('mw_stop') if param is not None: self._mw.stop_freq_DoubleSpinBox.blockSignals(True) self._mw.stop_freq_DoubleSpinBox.setValue(param) self._mw.stop_freq_DoubleSpinBox.blockSignals(False) param = param_dict.get('run_time') if param is not None: self._mw.runtime_DoubleSpinBox.blockSignals(True) self._mw.runtime_DoubleSpinBox.setValue(param) self._mw.runtime_DoubleSpinBox.blockSignals(False) param = param_dict.get('number_of_lines') if param is not None: self._sd.matrix_lines_SpinBox.blockSignals(True) self._sd.matrix_lines_SpinBox.setValue(param) self._sd.matrix_lines_SpinBox.blockSignals(False) param = param_dict.get('clock_frequency') if param is not None: self._sd.clock_frequency_DoubleSpinBox.blockSignals(True) self._sd.clock_frequency_DoubleSpinBox.setValue(param) self._sd.clock_frequency_DoubleSpinBox.blockSignals(False) param = param_dict.get('oversampling') if param is not None: self._sd.oversampling_SpinBox.blockSignals(True) self._sd.oversampling_SpinBox.setValue(param) self._sd.oversampling_SpinBox.blockSignals(False) param = param_dict.get('lock_in') if param is not None: self._sd.lock_in_CheckBox.blockSignals(True) self._sd.lock_in_CheckBox.setChecked(param) self._sd.lock_in_CheckBox.blockSignals(False) param = param_dict.get('cw_mw_frequency') if param is not None: self._mw.cw_frequency_DoubleSpinBox.blockSignals(True) self._mw.cw_frequency_DoubleSpinBox.setValue(param) self._mw.cw_frequency_DoubleSpinBox.blockSignals(False) param = param_dict.get('cw_mw_power') if param is not None: self._mw.cw_power_DoubleSpinBox.blockSignals(True) self._mw.cw_power_DoubleSpinBox.setValue(param) self._mw.cw_power_DoubleSpinBox.blockSignals(False) param = param_dict.get('average_length') if param is not None: self._mw.average_level_SpinBox.blockSignals(True) self._mw.average_level_SpinBox.setValue(param) self._mw.average_level_SpinBox.blockSignals(False) return ############################################################################ # Change Methods # ############################################################################ def change_cw_params(self): """ Change CW frequency and power of microwave source """ frequency = self._mw.cw_frequency_DoubleSpinBox.value() power = self._mw.cw_power_DoubleSpinBox.value() self.sigMwCwParamsChanged.emit(frequency, power) return def change_sweep_params(self): """ Change start, stop and step frequency of frequency sweep """ start = self._mw.start_freq_DoubleSpinBox.value() stop = self._mw.stop_freq_DoubleSpinBox.value() step = self._mw.step_freq_DoubleSpinBox.value() power = self._mw.sweep_power_DoubleSpinBox.value() self.sigMwSweepParamsChanged.emit(start, stop, step, power) return def change_runtime(self): """ Change time after which microwave sweep is stopped """ runtime = self._mw.runtime_DoubleSpinBox.value() self.sigRuntimeChanged.emit(runtime) return def save_data(self): """ Save the sum plot, the scan marix plot and the scan data """ filetag = self._mw.save_tag_LineEdit.text() cb_range = self.get_matrix_cb_range() # Percentile range is None, unless the percentile scaling is selected in GUI. pcile_range = None if self._mw.odmr_cb_centiles_RadioButton.isChecked(): low_centile = self._mw.odmr_cb_low_percentile_DoubleSpinBox.value() high_centile = self._mw.odmr_cb_high_percentile_DoubleSpinBox.value( ) pcile_range = [low_centile, high_centile] self.sigSaveMeasurement.emit(filetag, cb_range, pcile_range) return
class CameraGUI(GUIBase): """ Main spectrometer camera class. """ camera_logic = Connector(interface='CameraLogic') savelogic = Connector(interface='SaveLogic') sigVideoStart = QtCore.Signal() sigVideoStop = QtCore.Signal() sigImageStart = QtCore.Signal() sigImageStop = QtCore.Signal() sigROISet = QtCore.Signal(dict) _image = [] _logic = None _mw = None def __init__(self, config, **kwargs): # load connection super().__init__(config=config, **kwargs) def on_activate(self): """ Initializes all needed UI files and establishes the connectors. """ self._logic = self.camera_logic() self._save_logic = self.savelogic() # Windows self._mw = CameraWindow() self._mw.centralwidget.hide() self._mw.setDockNestingEnabled(True) self.initSettingsUI() self._sd.exposureDSpinBox.setDecimals(5) self._mw.start_video_Action.setEnabled(True) self._mw.start_video_Action.setChecked(self._logic.enabled) self._mw.start_video_Action.triggered.connect(self.start_video_clicked) self._mw.start_image_Action.setEnabled(True) self._mw.start_image_Action.setChecked(self._logic.enabled) self._mw.start_image_Action.triggered.connect(self.start_image_clicked) self._mw.action_toggle_cooling.toggled.connect(self.toggle_fan) self._logic.sigUpdateDisplay.connect(self.update_data) self._logic.sigAcquisitionFinished.connect(self.acquisition_finished) self._logic.sigVideoFinished.connect(self.enable_start_image_action) # starting the physical measurement self.sigVideoStart.connect(self._logic.start_loop) self.sigVideoStop.connect(self._logic.stop_loop) self.sigImageStart.connect(self._logic.start_single_acquistion) # connect Settings action under Options menu self._mw.actionSettings.triggered.connect(self.menu_settings) # connect save action to save function self._mw.actionSave_XY_Scan.triggered.connect(self.save_xy_scan_data) raw_data_image = self._logic.get_last_image() # This allows the camera GUI to take care of a 3darray of images if the cam GUI is initialized after # and ODMR measuremnt. try: if raw_data_image.ndim > 2: raw_data_image = np.zeros(self._logic.get_sensor()) except BaseException: pass self._image = pg.ImageItem(image=raw_data_image, axisOrder='row-major') self._mw.image_PlotWidget.addItem(self._image) # Set ROI widget with default sensor size, snapping true and invisible color until and image is clicked. # Extra scale handles are added as well. # It has not been added to main window yet, so as to give a clean look when an image has not yet been # clicked. self.roi_p1 = self.roi_p2 = 0 self.roi_s1, self.roi_s2 = self._logic.get_sensor() self.roi = pg.RectROI([self.roi_p1, self.roi_p2], [self.roi_s1, self.roi_s2], pen=(0, 0, 0, 0), scaleSnap=True, translateSnap=True, maxBounds=QtCore.QRectF(self.roi_p1, self.roi_p2, self.roi_s1, self.roi_s2), movable=False) self.roi.handleSize = 12 self.roi.addScaleHandle((0, 1), (1, 0)) self.roi.addScaleHandle((0, 0), (1, 1)) self.roi.addScaleHandle((1, 0), (0, 1)) self.roi.addTranslateHandle((1, 1), (0, 0)) # self._mw.image_PlotWidget.addItem(self.roi) self._mw.image_PlotWidget.setAspectLocked(True) self.sigROISet.connect(self._logic.set_image_roi) # ROI button actions self._mw.DefaultRoi.clicked.connect(self.default_roi) self._mw.SetRoi.clicked.connect(self.set_roi) self._mw.DefaultRoi.setEnabled(False) self._mw.SetRoi.setEnabled(False) self._mw.image_PlotWidget.addItem(self.roi) self.cross = pg.CrosshairROI(pos=(self.roi_s1 / 2, self.roi_s2 / 2), size=(40, 40), translateSnap=True, rotateSnap=True, maxBounds=QtCore.QRectF(0, 0, 1200, 1200)) self.cross.sigRegionChanged.connect(self.print_counts) self._mw.image_PlotWidget.addItem(self.cross) self.scaleBar = pg.LineSegmentROI(([0, 0], [100, 0]), pen={ 'color': "#E0D8D8", 'width': 3 }) self._mw.image_PlotWidget.addItem(self.scaleBar) self.scaleBar.sigRegionChanged.connect(self.print_scale) # Get the colorscale and set the LUTs self.my_colors = ColorScaleInferno() self._image.setLookupTable(self.my_colors.lut) # Connect the buttons and inputs for the colorbar self._mw.xy_cb_manual_RadioButton.clicked.connect( self.update_xy_cb_range) self._mw.xy_cb_centiles_RadioButton.clicked.connect( self.update_xy_cb_range) self._mw.xy_cb_min_DoubleSpinBox.valueChanged.connect( self.shortcut_to_xy_cb_manual) self._mw.xy_cb_max_DoubleSpinBox.valueChanged.connect( self.shortcut_to_xy_cb_manual) self._mw.xy_cb_low_percentile_DoubleSpinBox.valueChanged.connect( self.shortcut_to_xy_cb_centiles) self._mw.xy_cb_high_percentile_DoubleSpinBox.valueChanged.connect( self.shortcut_to_xy_cb_centiles) # create color bar self.xy_cb = ColorBar(self.my_colors.cmap_normed, width=100, cb_min=0, cb_max=100) self.depth_cb = ColorBar(self.my_colors.cmap_normed, width=100, cb_min=0, cb_max=100) self._mw.xy_cb_ViewWidget.addItem(self.xy_cb) self._mw.xy_cb_ViewWidget.hideAxis('bottom') self._mw.xy_cb_ViewWidget.setLabel('left', 'Fluorescence', units='c') self._mw.xy_cb_ViewWidget.setMouseEnabled(x=False, y=False) def on_deactivate(self): """ Deinitialisation performed during deactivation of the module. """ self._mw.close() def show(self): """Make window visible and put it above all other windows. """ QtWidgets.QMainWindow.show(self._mw) self._mw.activateWindow() self._mw.raise_() def initSettingsUI(self): """ Definition, configuration and initialisation of the settings GUI. This init connects all the graphic modules, which were created in the *.ui file and configures the event handling between the modules. Moreover it sets default values if not existed in the logic modules. """ # Create the Settings window self._sd = CameraSettingDialog() # Connect the action of the settings window with the code: self._sd.accepted.connect(self.update_settings) self._sd.rejected.connect(self.keep_former_settings) self._sd.buttonBox.button( QtWidgets.QDialogButtonBox.Apply).clicked.connect( self.update_settings) # write the configuration to the settings window of the GUI. self.keep_former_settings() def update_settings(self): """ Write new settings from the gui to the file. """ self._logic.set_exposure(self._sd.exposureDSpinBox.value()) self._logic.set_gain(self._sd.gainSpinBox.value()) exp_res = self._sd.rescomboBox.currentIndex() self._logic.set_exposure_resolution(exp_res) def keep_former_settings(self): """ Keep the old settings and restores them in the gui. """ self._sd.exposureDSpinBox.setValue(self._logic._exposure) self._sd.gainSpinBox.setValue(self._logic._gain) if self._sd.rescomboBox.currentIndex() == 0: self._sd.exposureDSpinBox.setMaximum(10000000) else: self._sd.exposureDSpinBox.setMaximum(1000000000) def menu_settings(self): """ This method opens the settings menu. """ self._sd.exec_() def start_image_clicked(self): # Adds the ROI widget when an image is clicked. Only serves aesthetic purpose when added here and not # in initialization. # self._mw.image_PlotWidget.addItem(self.roi) self.sigImageStart.emit() self._mw.start_image_Action.setDisabled(True) self._mw.start_video_Action.setDisabled(True) def acquisition_finished(self): self._mw.start_image_Action.setChecked(False) self._mw.start_image_Action.setDisabled(False) self._mw.start_video_Action.setDisabled(False) def start_video_clicked(self): """ Handling the Start button to stop and restart the counter. """ self._mw.start_image_Action.setDisabled(True) # self._mw.image_PlotWidget.addItem(self.roi) if self._logic.enabled: self._mw.start_video_Action.setText('Start Video') self.sigVideoStop.emit() else: self._mw.start_video_Action.setText('Stop Video') self.sigVideoStart.emit() def enable_start_image_action(self): self._mw.start_image_Action.setEnabled(True) def update_data(self): """ Get the image data from the logic and print it on the window """ raw_data_image = self._logic.get_last_image() # levels = (0., 1.) # The button for ROI are enabled here, as well as the pen drawing the # ROI is given color. self._mw.DefaultRoi.setEnabled(True) self._mw.SetRoi.setEnabled(True) self.roi.setPen((6, 9)) self._image.setImage(image=raw_data_image) self.update_xy_cb_range() self._sd.exposureDSpinBox.setValue(self._logic._exposure) self.print_counts() # self._image.setImage(image=raw_data_image, levels=levels) def print_counts(self): x, y = self.cross.pos() self.counts = self._logic.get_last_image()[int(y), int(x)] self._mw.counts_label.setText(str(self.counts)) def print_scale(self): x1, y1, x2, y2 = self.scaleBar.boundingRect().getCoords() px_um = self._mw.dist_px.value() self.dist = np.sqrt((x1 - x2)**2 + (y1 - y2)**2) * px_um self._mw.dist_label.setText(f'{self.dist:.3f}') def updateView(self): """ Update the view when the model change """ pass def update_shape(self): '''Not used but for updating the default ROI shape to sensor size. ''' self.roi_s1, self.roi_s2 = self._logic.get_sensor() def default_roi(self): '''Sets the ROI to initialized defualt coords. set_roi is called as well to update the image and update camera as well of the new ROI coords. ''' self.roi.setPos((self.roi_p1, self.roi_p2)) self.roi.setSize((self.roi_s1, self.roi_s2)) self.set_roi() def set_roi(self): '''The ROI coord. dict. is emitted to update the camera of the coorect ROI, starts image updation by calling the image_clicked function and repositons the ROI to fit the new ROIed imaged. ''' was_enabled = False if self.roi.saveState()['size'] < (3, 3): return if self._logic.enabled: self._mw.start_video_Action.setText('Start Video') self.sigVideoStop.emit() was_enabled = True self.sigROISet.emit(self.roi.saveState()) self.start_image_clicked() self.roi.setSize(self.roi.saveState()['size']) self.cross.maxBounds = QtCore.QRectF( 0, 0, self.roi.saveState()['size'][0] - 1, self.roi.saveState()['size'][1] - 1) self.cross.setPos((self.roi.saveState()['size'][0] / 2, self.roi.saveState()['size'][1] / 2)) self.roi.setPos((self.roi_p1, self.roi_p2)) if was_enabled: self._mw.start_video_Action.setText('Stop Video') self.sigVideoStart.emit() was_enabled = False def toggle_fan(self): fan_speed = 3 if self._mw.action_toggle_cooling.isChecked() else 0 self._logic.set_fan_speed(fan_speed) # color bar functions def get_xy_cb_range(self): """ Determines the cb_min and cb_max values for the xy scan image """ # If "Manual" is checked, or the image data is empty (all zeros), then # take manual cb range. if self._mw.xy_cb_manual_RadioButton.isChecked() or np.max( self._image.image) == 0.0: cb_min = self._mw.xy_cb_min_DoubleSpinBox.value() cb_max = self._mw.xy_cb_max_DoubleSpinBox.value() # Otherwise, calculate cb range from percentiles. else: # xy_image_nonzero = self._image.image[np.nonzero(self._image.image)] # Read centile range low_centile = self._mw.xy_cb_low_percentile_DoubleSpinBox.value() high_centile = self._mw.xy_cb_high_percentile_DoubleSpinBox.value() cb_min = np.percentile(self._image.image, low_centile) cb_max = np.percentile(self._image.image, high_centile) cb_range = [cb_min, cb_max] return cb_range def refresh_xy_colorbar(self): """ Adjust the xy colorbar. Calls the refresh method from colorbar, which takes either the lowest and higherst value in the image or predefined ranges. Note that you can invert the colorbar if the lower border is bigger then the higher one. """ cb_range = self.get_xy_cb_range() self.xy_cb.refresh_colorbar(cb_range[0], cb_range[1]) def refresh_xy_image(self): """ Update the current XY image from the logic. Everytime the scanner is scanning a line in xy the image is rebuild and updated in the GUI. """ self._image.getViewBox().updateAutoRange() xy_image_data = self._logic._last_image cb_range = self.get_xy_cb_range() # Now update image with new color scale, and update colorbar self._image.setImage(image=xy_image_data, levels=(cb_range[0], cb_range[1])) self.refresh_xy_colorbar() def shortcut_to_xy_cb_manual(self): """Someone edited the absolute counts range for the xy colour bar, better update.""" self._mw.xy_cb_manual_RadioButton.setChecked(True) self.update_xy_cb_range() def shortcut_to_xy_cb_centiles(self): """Someone edited the centiles range for the xy colour bar, better update.""" self._mw.xy_cb_centiles_RadioButton.setChecked(True) self.update_xy_cb_range() def update_xy_cb_range(self): """Redraw xy colour bar and scan image.""" self.refresh_xy_colorbar() self.refresh_xy_image() # save functions def save_xy_scan_data(self): """ Run the save routine from the logic to save the xy confocal data.""" cb_range = self.get_xy_cb_range() # Percentile range is None, unless the percentile scaling is selected # in GUI. pcile_range = None if not self._mw.xy_cb_manual_RadioButton.isChecked(): low_centile = self._mw.xy_cb_low_percentile_DoubleSpinBox.value() high_centile = self._mw.xy_cb_high_percentile_DoubleSpinBox.value() pcile_range = [low_centile, high_centile] self._logic.save_xy_data(colorscale_range=cb_range, percentile_range=pcile_range) # TODO: find a way to produce raw image in savelogic. For now it is # saved here. filepath = self._save_logic.get_path_for_module(module_name='Confocal') filename = filepath + os.sep + \ time.strftime('%Y%m%d-%H%M-%S_confocal_xy_scan_raw_pixel_image') self._image.save(filename + '_raw.png')
class PoiManagerGui(GUIBase): """ This is the GUI Class for PoiManager """ _modclass = 'PoiManagerGui' _modtype = 'gui' # declare connectors poimanagerlogic1 = Connector(interface='PoiManagerLogic') confocallogic1 = Connector(interface='ConfocalLogic') def __init__(self, config, **kwargs): super().__init__(config=config, **kwargs) def on_activate(self): """ Initializes the overall GUI, and establishes the connectors. This method executes the init methods for each of the GUIs. """ # Connectors self._poi_manager_logic = self.get_connector('poimanagerlogic1') self._confocal_logic = self.get_connector('confocallogic1') self.log.debug("POI Manager logic is {0}".format(self._poi_manager_logic)) self.log.debug("Confocal logic is {0}".format(self._confocal_logic)) # Initializing the GUIs self.initMainUI() self.initReorientRoiDialogUI() # There could be POIs created in the logic already, so update lists and map self.populate_poi_list() self._redraw_sample_shift() self._redraw_poi_markers() def mouseMoved(self, event): """ Handles any mouse movements inside the image. @param event: Event that signals the new mouse movement. This should be of type QPointF. Gets the mouse position, converts it to a position scaled to the image axis and than calculates and updated the position to the current POI. """ # converts the absolute mouse position to a position relative to the axis mouse_point=self.roi_map_image.mapFromScene(event.toPoint()) #self.log.debug("Mouse at x = {0:0.2e}, y = {1:0.2e}".format(mouse_point.x(), mouse_point.y())) # only calculate distance, if a POI is selected if self._poi_manager_logic.active_poi is not None: cur_poi_pos = self._poi_manager_logic.get_poi_position(poikey=self._poi_manager_logic.active_poi.get_key()) self._mw.poi_distance_label.setText('{0:.2e} ({1:.2e}, {2:.2e})'.format( np.sqrt((mouse_point.x() - cur_poi_pos[0])**2+(mouse_point.y() - cur_poi_pos[1])**2), mouse_point.x() - cur_poi_pos[0], mouse_point.y() - cur_poi_pos[1])) def initMainUI(self): """ Definition, configuration and initialisation of the POI Manager GUI. This init connects all the graphic modules, which were created in the *.ui file and configures the event handling between the modules. """ # Use the inherited class 'Ui_PoiManagerGuiTemplate' to create now the # GUI element: self._mw = PoiManagerMainWindow() ##################### # Configuring the dock widgets ##################### # All our gui elements are dockable, and so there should be no "central" widget. self._mw.centralwidget.hide() self._mw.setDockNestingEnabled(True) self._mw.roi_cb_high_percentile_DoubleSpinBox.setOpts(step=0.01, decimals=5) self._mw.roi_cb_low_percentile_DoubleSpinBox.setOpts(step=0.01, decimals=2) ##################### # Setting up display of ROI map xy image ##################### # Get the image for the display from the logic: self.roi_xy_image_data = self._poi_manager_logic.roi_map_data[:, :, 3] # Load the image in the display: self.roi_map_image = pg.ImageItem(image=self.roi_xy_image_data, axisOrder='row-major') self.roi_map_image.setRect( QtCore.QRectF( self._confocal_logic.image_x_range[0], self._confocal_logic.image_y_range[0], self._confocal_logic.image_x_range[1] - self._confocal_logic.image_x_range[0], self._confocal_logic.image_y_range[1] - self._confocal_logic.image_y_range[0])) # Add the display item to the roi map ViewWidget defined in the UI file self._mw.roi_map_ViewWidget.addItem(self.roi_map_image) self._mw.roi_map_ViewWidget.setLabel('bottom', 'X position', units='m') self._mw.roi_map_ViewWidget.setLabel('left', 'Y position', units='m') # Set to fixed 1.0 aspect ratio, since the metaphor is a "map" of the sample self._mw.roi_map_ViewWidget.setAspectLocked(lock=True, ratio=1.0) # Get the colorscales and set LUT my_colors = ColorScaleInferno() self.roi_map_image.setLookupTable(my_colors.lut) # Add color bar: self.roi_cb = ColorBar(my_colors.cmap_normed, 100, 0, 100000) self._mw.roi_cb_ViewWidget.addItem(self.roi_cb) self._mw.roi_cb_ViewWidget.hideAxis('bottom') self._mw.roi_cb_ViewWidget.setLabel('left', 'Fluorescence', units='c/s') self._mw.roi_cb_ViewWidget.setMouseEnabled(x=False, y=False) ##################### # Setting up display of sample shift plot ##################### # Load image in the display self.x_shift_plot = pg.PlotDataItem( [0], [0], pen=pg.mkPen(palette.c1, style=QtCore.Qt.DotLine), symbol='o', symbolPen=palette.c1, symbolBrush=palette.c1, symbolSize=5, name='x' ) self.y_shift_plot = pg.PlotDataItem( [0], [0], pen=pg.mkPen(palette.c2, style=QtCore.Qt.DotLine), symbol='s', symbolPen=palette.c2, symbolBrush=palette.c2, symbolSize=5, name='y' ) self.z_shift_plot = pg.PlotDataItem( [0], [0], pen=pg.mkPen(palette.c3, style=QtCore.Qt.DotLine), symbol='t', symbolPen=palette.c3, symbolBrush=palette.c3, symbolSize=5, name='z' ) self._mw.sample_shift_ViewWidget.addLegend() # Add the plot to the ViewWidget defined in the UI file self._mw.sample_shift_ViewWidget.addItem(self.x_shift_plot) self._mw.sample_shift_ViewWidget.addItem(self.y_shift_plot) self._mw.sample_shift_ViewWidget.addItem(self.z_shift_plot) # Label axes self._mw.sample_shift_ViewWidget.setLabel('bottom', 'Time', units='s') self._mw.sample_shift_ViewWidget.setLabel('left', 'Sample shift', units='m') ##################### # Connect signals ##################### # Distance Measurement: # Introducing a SignalProxy will limit the rate of signals that get fired. # Otherwise we will run into a heap of unhandled function calls. proxy = pg.SignalProxy(self.roi_map_image.scene().sigMouseMoved, rateLimit=60, slot=self.mouseMoved) # Connecting a Mouse Signal to trace to mouse movement function. self.roi_map_image.scene().sigMouseMoved.connect(self.mouseMoved) # Toolbar actions self._mw.new_roi_Action.triggered.connect(self.make_new_roi) self._mw.save_roi_Action.triggered.connect(self.save_roi) self._mw.load_roi_Action.triggered.connect(self.load_roi) self._mw.reorient_roi_Action.triggered.connect(self.open_reorient_roi_dialog) self._mw.autofind_pois_Action.triggered.connect(self.do_autofind_poi_procedure) self._mw.optimize_roi_Action.triggered.connect(self.optimize_roi) self._mw.new_poi_Action.triggered.connect(self.set_new_poi) self._mw.goto_poi_Action.triggered.connect(self.goto_poi) self._mw.refind_poi_Action.triggered.connect(self.update_poi_pos) self._mw.track_poi_Action.triggered.connect(self.toggle_tracking) # Interface controls self._mw.get_confocal_image_PushButton.clicked.connect(self.get_confocal_image) self._mw.set_poi_PushButton.clicked.connect(self.set_new_poi) self._mw.delete_last_pos_Button.clicked.connect(self.delete_last_point) self._mw.manual_update_poi_PushButton.clicked.connect(self.manual_update_poi) self._mw.move_poi_PushButton.clicked.connect(self.move_poi) self._mw.poi_name_LineEdit.returnPressed.connect(self.change_poi_name) self._mw.roi_name_LineEdit.editingFinished.connect(self.set_roi_name) self._mw.delete_poi_PushButton.clicked.connect(self.delete_poi) self._mw.goto_poi_after_update_checkBox.toggled.connect(self.toggle_follow) # This needs to be activated so that it only listens to user input, and ignores # algorithmic index changes self._mw.active_poi_ComboBox.activated.connect(self.handle_active_poi_ComboBox_index_change) self._mw.refind_method_ComboBox.currentIndexChanged.connect(self.change_refind_method) # Connect the buttons and inputs for the colorbar self._mw.roi_cb_centiles_RadioButton.toggled.connect(self.refresh_roi_colorscale) self._mw.roi_cb_manual_RadioButton.toggled.connect(self.refresh_roi_colorscale) self._mw.roi_cb_min_SpinBox.valueChanged.connect(self.shortcut_to_roi_cb_manual) self._mw.roi_cb_max_SpinBox.valueChanged.connect(self.shortcut_to_roi_cb_manual) self._mw.roi_cb_low_percentile_DoubleSpinBox.valueChanged.connect(self.shortcut_to_roi_cb_centiles) self._mw.roi_cb_high_percentile_DoubleSpinBox.valueChanged.connect(self.shortcut_to_roi_cb_centiles) self._mw.display_shift_vs_duration_RadioButton.toggled.connect(self._redraw_sample_shift) self._mw.display_shift_vs_clocktime_RadioButton.toggled.connect(self._redraw_sample_shift) self._markers = dict() # Signal at end of refocus self._poi_manager_logic.signal_timer_updated.connect( self._update_timer, QtCore.Qt.QueuedConnection ) self._poi_manager_logic.signal_poi_updated.connect( self._redraw_sample_shift, QtCore.Qt.QueuedConnection ) self._poi_manager_logic.signal_poi_updated.connect( self.populate_poi_list, QtCore.Qt.QueuedConnection ) self._poi_manager_logic.signal_poi_updated.connect( self._redraw_poi_markers, QtCore.Qt.QueuedConnection ) self._poi_manager_logic.signal_poi_deleted.connect( self._remove_poi_marker ) self._poi_manager_logic.signal_confocal_image_updated.connect( self._redraw_roi_image ) self._poi_manager_logic.signal_periodic_opt_duration_changed.connect( self._track_period_changed ) self._poi_manager_logic.signal_periodic_opt_started.connect( self._tracking_started ) self._poi_manager_logic.signal_periodic_opt_stopped.connect( self._tracking_stopped ) # Connect track period after setting the GUI value from the logic initial_period = self._poi_manager_logic.timer_duration self._mw.track_period_SpinBox.setValue(initial_period) self._mw.time_till_next_update_ProgressBar.setMaximum(initial_period) self._mw.time_till_next_update_ProgressBar.setValue(initial_period) self._mw.track_period_SpinBox.valueChanged.connect(self.set_track_period) # Redraw the sample_shift axes if the range changes self._mw.sample_shift_ViewWidget.plotItem.sigRangeChanged.connect(self._redraw_sample_shift) self._mw.show() def initReorientRoiDialogUI(self): """ Definition, configuration and initialization fo the Reorient ROI Dialog GUI. This init connects all the graphic modules which were created in the *.ui file and configures event handling. """ # Create the Reorient ROI Dialog window self._rrd = ReorientRoiDialog() # Connect the QDialog buttons to methods in the GUI self._rrd.accepted.connect(self.do_roi_reorientation) self._rrd.rejected.connect(self.reset_reorientation_dialog) # Connect the at_crosshair buttons self._rrd.ref_a_at_crosshair_PushButton.clicked.connect(self.ref_a_at_crosshair) self._rrd.ref_b_at_crosshair_PushButton.clicked.connect(self.ref_b_at_crosshair) self._rrd.ref_c_at_crosshair_PushButton.clicked.connect(self.ref_c_at_crosshair) # Connect input value changes to update the sanity-check values self._rrd.ref_a_poi_ComboBox.activated.connect(self.reorientation_sanity_check) self._rrd.ref_b_poi_ComboBox.activated.connect(self.reorientation_sanity_check) self._rrd.ref_c_poi_ComboBox.activated.connect(self.reorientation_sanity_check) self._rrd.ref_a_x_pos_DoubleSpinBox.valueChanged.connect(self.reorientation_sanity_check) self._rrd.ref_a_y_pos_DoubleSpinBox.valueChanged.connect(self.reorientation_sanity_check) self._rrd.ref_a_z_pos_DoubleSpinBox.valueChanged.connect(self.reorientation_sanity_check) self._rrd.ref_b_x_pos_DoubleSpinBox.valueChanged.connect(self.reorientation_sanity_check) self._rrd.ref_b_y_pos_DoubleSpinBox.valueChanged.connect(self.reorientation_sanity_check) self._rrd.ref_b_z_pos_DoubleSpinBox.valueChanged.connect(self.reorientation_sanity_check) self._rrd.ref_c_x_pos_DoubleSpinBox.valueChanged.connect(self.reorientation_sanity_check) self._rrd.ref_c_y_pos_DoubleSpinBox.valueChanged.connect(self.reorientation_sanity_check) self._rrd.ref_c_z_pos_DoubleSpinBox.valueChanged.connect(self.reorientation_sanity_check) def on_deactivate(self): """ Deinitialisation performed during deactivation of the module. """ self._mw.close() def show(self): """Make main window visible and put it above all other windows. """ QtWidgets.QMainWindow.show(self._mw) self._mw.activateWindow() self._mw.raise_() def get_confocal_image(self): """ Update the roi_map_data in poi manager logic, and use this updated data to redraw an image of the ROI. """ # Make poi manager logic get the confocal data self._poi_manager_logic.get_confocal_image_data() def _redraw_roi_image(self): # the image data is the fluorescence part self.roi_xy_image_data = self._poi_manager_logic.roi_map_data[:, :, 3] # Also get the x and y range limits and hold them locally self.roi_map_xmin = np.min(self._poi_manager_logic.roi_map_data[:, :, 0]) self.roi_map_xmax = np.max(self._poi_manager_logic.roi_map_data[:, :, 0]) self.roi_map_ymin = np.min(self._poi_manager_logic.roi_map_data[:, :, 1]) self.roi_map_ymax = np.max(self._poi_manager_logic.roi_map_data[:, :, 1]) self.roi_map_image.getViewBox().enableAutoRange() self.roi_map_image.setRect( QtCore.QRectF( self.roi_map_xmin, self.roi_map_ymin, self.roi_map_xmax - self.roi_map_xmin, self.roi_map_ymax - self.roi_map_ymin)) self.roi_map_image.setImage(image=self.roi_xy_image_data, autoLevels=True) def shortcut_to_roi_cb_manual(self): self._mw.roi_cb_manual_RadioButton.setChecked(True) self.refresh_roi_colorscale() def shortcut_to_roi_cb_centiles(self): self._mw.roi_cb_centiles_RadioButton.setChecked(True) self.refresh_roi_colorscale() def refresh_roi_colorscale(self): """ Adjust the colorbar in the ROI xy image, and update the image with the new color scale. Calls the refresh method from colorbar, which takes either the lowest and higherst value in the image or predefined ranges. Note that you can invert the colorbar if the lower border is bigger then the higher one. """ cb_min, cb_max = self.determine_cb_range() self.roi_map_image.setImage(image=self.roi_xy_image_data, levels=(cb_min, cb_max)) self.roi_cb.refresh_colorbar(cb_min, cb_max) self._mw.roi_cb_ViewWidget.update() def determine_cb_range(self): """ Process UI input to determine color bar range""" # If "Centiles" is checked, adjust colour scaling automatically to centiles. # Otherwise, take user-defined values. if self._mw.roi_cb_centiles_RadioButton.isChecked(): low_centile = self._mw.roi_cb_low_percentile_DoubleSpinBox.value() high_centile = self._mw.roi_cb_high_percentile_DoubleSpinBox.value() cb_min = np.percentile(self.roi_xy_image_data, low_centile) cb_max = np.percentile(self.roi_xy_image_data, high_centile) else: cb_min = self._mw.roi_cb_min_SpinBox.value() cb_max = self._mw.roi_cb_max_SpinBox.value() return cb_min, cb_max def set_new_poi(self): """ This method sets a new poi from the current crosshair position.""" key = self._poi_manager_logic.add_poi() def delete_last_point(self): """ Delete the last track position of a chosen poi. """ if self._poi_manager_logic.active_poi is None: self.log.warning("No POI selected. No datapoint can be deleted") else: self._poi_manager_logic.delete_last_position(poikey=self._poi_manager_logic.active_poi.get_key()) def delete_poi(self): """ Delete the active poi from the list of managed points. """ if self._poi_manager_logic.active_poi is None: self.log.warning("No POI selected.") else: key = self._poi_manager_logic.active_poi.get_key() # todo: this needs to handle the case where the logic deletes a POI. self._poi_manager_logic.delete_poi(poikey=key) def _remove_poi_marker(self, poikey): """ Remove the POI marker for a POI that was deleted. """ self._markers[poikey].delete_from_viewwidget() del self._markers[poikey] def manual_update_poi(self): """ Manually adds a point to the trace of a given poi without refocussing, and uses that information to update sample position. """ if self._poi_manager_logic.active_poi is None: self.log.warning("No POI selected.") else: self._poi_manager_logic.set_new_position(poikey=self._poi_manager_logic.active_poi.get_key()) def move_poi(self): """Manually move a POI to a new location in the sample map, but WITHOUT changing the sample position. This moves a POI relative to all the others. """ if self._poi_manager_logic.active_poi is None: self.log.warning("No POI selected.") else: self._poi_manager_logic.move_coords(poikey=self._poi_manager_logic.active_poi.get_key()) def toggle_tracking(self): if self._poi_manager_logic.active_poi is None: self.log.warning("No POI selected.") else: if self._poi_manager_logic.timer is None: self._poi_manager_logic.start_periodic_refocus(poikey=self._poi_manager_logic.active_poi.get_key()) else: self._poi_manager_logic.stop_periodic_refocus() def _tracking_started(self): self._mw.track_poi_Action.setChecked(True) def _tracking_stopped(self): self._mw.track_poi_Action.setChecked(False) def goto_poi(self, key): """ Go to the last known position of poi <key>.""" if self._poi_manager_logic.active_poi is None: self.log.warning("No POI selected.") else: self._poi_manager_logic.go_to_poi(poikey=self._poi_manager_logic.active_poi.get_key()) def populate_poi_list(self): """ Populate the dropdown box for selecting a poi. """ self.log.debug('started populate_poi_list at {0}'.format(time.time())) self._mw.active_poi_ComboBox.clear() self._mw.offset_anchor_ComboBox.clear() self._rrd.ref_a_poi_ComboBox.clear() self._rrd.ref_b_poi_ComboBox.clear() self._rrd.ref_c_poi_ComboBox.clear() for key in self._poi_manager_logic.get_all_pois(abc_sort=True): if key is not 'crosshair' and key is not 'sample': poi_list_empty = False self._mw.active_poi_ComboBox.addItem( self._poi_manager_logic.poi_list[key].get_name(), key) self._mw.offset_anchor_ComboBox.addItem( self._poi_manager_logic.poi_list[key].get_name(), key) self._rrd.ref_a_poi_ComboBox.addItem( self._poi_manager_logic.poi_list[key].get_name(), key) self._rrd.ref_b_poi_ComboBox.addItem( self._poi_manager_logic.poi_list[key].get_name(), key) self._rrd.ref_c_poi_ComboBox.addItem( self._poi_manager_logic.poi_list[key].get_name(), key) # If there is no active POI, set the combobox to nothing (-1) if self._poi_manager_logic.active_poi is None: self._mw.active_poi_ComboBox.setCurrentIndex(-1) # Otherwise, set it to the active POI else: self._mw.active_poi_ComboBox.setCurrentIndex( self._mw.active_poi_ComboBox.findData( self._poi_manager_logic.active_poi.get_key() ) ) self.log.debug('finished populating at '.format(time.time())) def change_refind_method(self): """ Make appropriate changes in the GUI to reflect the newly chosen refind method.""" if self._mw.refind_method_ComboBox.currentText() == 'position optimisation': self._mw.offset_anchor_ComboBox.setEnabled(False) elif self._mw.refind_method_ComboBox.currentText() == 'offset anchor': self.log.error("Anchor method not fully implemented yet. " "Feel free to fix this method. Using position optimisation instead.") self._mw.offset_anchor_ComboBox.setEnabled(True) else: # TODO: throw an error self.log.debug('error 123') def set_roi_name(self): """ Set the name of a ROI (useful when saving).""" self._poi_manager_logic.roi_name = self._mw.roi_name_LineEdit.text().replace(" ", "_") def change_poi_name(self): """ Change the name of a poi.""" newname = self._mw.poi_name_LineEdit.text() self._poi_manager_logic.rename_poi(poikey=self._poi_manager_logic.active_poi.get_key(), name=newname) # After POI name is changed, empty name field self._mw.poi_name_LineEdit.setText('') def handle_active_poi_ComboBox_index_change(self): """ Handle the change of index in the active POI combobox.""" key = self._mw.active_poi_ComboBox.itemData(self._mw.active_poi_ComboBox.currentIndex()) self._poi_manager_logic.set_active_poi(poikey = key) self._redraw_poi_markers() # todo when line 660 signal in logic is done, this is not necessary def select_poi_from_marker(self, poikey=None): """ Process the selection of a POI from click on POImark.""" # Keep track of selected POI self._poi_manager_logic.set_active_poi(poikey=poikey) # # Set the selected POI in the combobox # self._mw.active_poi_ComboBox.setCurrentIndex(self._mw.active_poi_ComboBox.findData(poikey)) # self._redraw_poi_markers() def update_poi_pos(self): if self._poi_manager_logic.active_poi is None: self.log.warning("No POI selected.") else: if self._mw.refind_method_ComboBox.currentText() == 'position optimisation': self._poi_manager_logic.optimise_poi(poikey=self._poi_manager_logic.active_poi.get_key()) elif self._mw.refind_method_ComboBox.currentText() == 'offset anchor': anchor_key = self._mw.offset_anchor_ComboBox.itemData( self._mw.offset_anchor_ComboBox.currentIndex()) self._poi_manager_logic.optimise_poi(poikey=self._poi_manager_logic.active_poi.get_key(), anchorkey=anchor_key) def toggle_follow(self): if self._mw.goto_poi_after_update_checkBox.isChecked(): self._poi_manager_logic.go_to_crosshair_after_refocus = False else: self._poi_manager_logic.go_to_crosshair_after_refocus = True def _update_timer(self): self._mw.time_till_next_update_ProgressBar.setValue(self._poi_manager_logic.time_left) def set_track_period(self): """ Change the progress bar and update the timer duration.""" new_track_period = self._mw.track_period_SpinBox.value() self._poi_manager_logic.set_periodic_optimize_duration(duration=new_track_period) def _track_period_changed(self): """ Reflect the changed track period in the GUI elements. """ new_track_period = self._poi_manager_logic.timer_duration # Set the new maximum for the progress bar self._mw.time_till_next_update_ProgressBar.setMaximum(new_track_period) # If the tracker is not active, then set the value of the progress bar to the # new maximum if not self._mw.track_poi_Action.isChecked(): self._mw.time_till_next_update_ProgressBar.setValue(new_track_period) def _redraw_clocktime_ticks(self): """If duration is displayed, reset ticks to default. Otherwise, create and update custom date/time ticks to the new axis range. """ myAxisItem = self._mw.sample_shift_ViewWidget.plotItem.axes['bottom']['item'] # if duration display, reset to default ticks if self._mw.display_shift_vs_duration_RadioButton.isChecked(): myAxisItem.setTicks(None) # otherwise, convert tick strings to clock format else: # determine size of the sample shift bottom axis item in pixels bounds = myAxisItem.mapRectFromParent(myAxisItem.geometry()) span = (bounds.topLeft(), bounds.topRight()) lengthInPixels = (span[1] - span[0]).manhattanLength() if lengthInPixels == 0: return -1 if myAxisItem.range[0] < 0: return -1 default_ticks = myAxisItem.tickValues( myAxisItem.range[0], myAxisItem.range[1], lengthInPixels) newticks = [] for i, tick_level in enumerate(default_ticks): newticks_this_level = [] ticks = tick_level[1] for ii, tick in enumerate(ticks): # For major ticks, include date if i == 0: string = time.strftime("%H:%M (%d.%m.)", time.localtime(tick * 3600)) # (the axis is plotted in hours to get naturally better placed ticks.) # for middle and minor ticks, just display clock time else: string = time.strftime("%H:%M", time.localtime(tick * 3600)) newticks_this_level.append((tick, string)) newticks.append(newticks_this_level) myAxisItem.setTicks(newticks) return 0 def _redraw_sample_shift(self): # Get trace data and calculate shifts in x,y,z poi_trace = self._poi_manager_logic.poi_list['sample'].get_position_history() # If duration display is checked, subtract initial time and convert to # mins or hours as appropriate if self._mw.display_shift_vs_duration_RadioButton.isChecked(): time_shift_data = (poi_trace[:, 0] - poi_trace[0, 0]) if np.max(time_shift_data) < 300: self._mw.sample_shift_ViewWidget.setLabel('bottom', 'Time elapsed', units='s') elif np.max(time_shift_data) < 7200: time_shift_data = time_shift_data / 60.0 self._mw.sample_shift_ViewWidget.setLabel('bottom', 'Time elapsed', units='min') else: time_shift_data = time_shift_data / 3600.0 self._mw.sample_shift_ViewWidget.setLabel('bottom', 'Time elapsed', units='hr') # Otherwise, take the actual time but divide by 3600 so that tickmarks # automatically fall on whole hours else: time_shift_data = poi_trace[:, 0] / 3600.0 self._mw.sample_shift_ViewWidget.setLabel('bottom', 'Time', units='') # Subtract initial position to get shifts x_shift_data = (poi_trace[:, 1] - poi_trace[0, 1]) y_shift_data = (poi_trace[:, 2] - poi_trace[0, 2]) z_shift_data = (poi_trace[:, 3] - poi_trace[0, 3]) # Plot data self.x_shift_plot.setData(time_shift_data, x_shift_data) self.y_shift_plot.setData(time_shift_data, y_shift_data) self.z_shift_plot.setData(time_shift_data, z_shift_data) self._redraw_clocktime_ticks() def _redraw_poi_markers(self): self.log.debug('starting redraw_poi_markers {0}'.format(time.time())) for key in self._poi_manager_logic.get_all_pois(): if key is not 'crosshair' and key is not 'sample': position = self._poi_manager_logic.get_poi_position(poikey=key) position = position[:2] if key in self._markers.keys(): self._markers[key].set_position(position) self._markers[key].deselect() else: # Create Region of Interest as marker: marker = PoiMark( position, poi=self._poi_manager_logic.poi_list[key], click_action=self.select_poi_from_marker, movable=False, scaleSnap=False, snapSize=1.0e-6) # Add to the Map Widget marker.add_to_viewwidget(self._mw.roi_map_ViewWidget) self._markers[key] = marker if self._poi_manager_logic.active_poi is not None: active_poi_key = self._poi_manager_logic.active_poi.get_key() self._markers[active_poi_key].select() cur_poi_pos = self._poi_manager_logic.get_poi_position(poikey=key) self._mw.poi_coords_label.setText( '({0:.2e}, {1:.2e}, {2:.2e})'.format(cur_poi_pos[0], cur_poi_pos[1], cur_poi_pos[2] ) ) self.log.debug('finished redraw at {0}'.format(time.time())) def make_new_roi(self): """ Start new ROI by removing all POIs and resetting the sample history.""" for key in self._poi_manager_logic.get_all_pois(): if key is not 'crosshair' and key is not 'sample': self._markers[key].delete_from_viewwidget() del self._markers self._markers = dict() self._poi_manager_logic.reset_roi() self.populate_poi_list() def save_roi(self): """ Save ROI to file.""" self._poi_manager_logic.save_poi_map_as_roi() def load_roi(self): """ Load a saved ROI from file.""" this_file = QtWidgets.QFileDialog.getOpenFileName( self._mw, str("Open ROI"), None, str("Data files (*.dat)"))[0] self._poi_manager_logic.load_roi_from_file(filename=this_file) self.populate_poi_list() def open_reorient_roi_dialog(self): """ Open the dialog for reorienting the ROI. """ self._rrd.show() def ref_a_at_crosshair(self): """ Set the newpos for ref A from the current crosshair position. """ # TODO: get the range for these spinboxes from the hardware scanner range! self._rrd.ref_a_x_pos_DoubleSpinBox.setValue(self._confocal_logic.get_position()[0]) self._rrd.ref_a_y_pos_DoubleSpinBox.setValue(self._confocal_logic.get_position()[1]) self._rrd.ref_a_z_pos_DoubleSpinBox.setValue(self._confocal_logic.get_position()[2]) def ref_b_at_crosshair(self): """ Set the newpos for ref B from the current crosshair position. """ self._rrd.ref_b_x_pos_DoubleSpinBox.setValue(self._confocal_logic.get_position()[0]) self._rrd.ref_b_y_pos_DoubleSpinBox.setValue(self._confocal_logic.get_position()[1]) self._rrd.ref_b_z_pos_DoubleSpinBox.setValue(self._confocal_logic.get_position()[2]) def ref_c_at_crosshair(self): """ Set the newpos for ref C from the current crosshair position. """ self._rrd.ref_c_x_pos_DoubleSpinBox.setValue(self._confocal_logic.get_position()[0]) self._rrd.ref_c_y_pos_DoubleSpinBox.setValue(self._confocal_logic.get_position()[1]) self._rrd.ref_c_z_pos_DoubleSpinBox.setValue(self._confocal_logic.get_position()[2]) def do_roi_reorientation(self): """Pass the old and new positions of refs A, B, C to PoiManager Logic to reorient every POI in the ROI. """ ref_a_coords, ref_b_coords, ref_c_coords, ref_a_newpos, ref_b_newpos, ref_c_newpos = self._read_reorient_roi_dialog_values() self._poi_manager_logic.reorient_roi(ref_a_coords, ref_b_coords, ref_c_coords, ref_a_newpos, ref_b_newpos, ref_c_newpos) # Clear the values in the Reorient Roi Dialog in case it is needed again self.reset_reorientation_dialog() def _read_reorient_roi_dialog_values(self): """ This reads the values from reorient ROI Dialog, and returns them. """ # Get POI keys for the chosen ref points ref_a_key = self._rrd.ref_a_poi_ComboBox.itemData(self._rrd.ref_a_poi_ComboBox.currentIndex()) ref_b_key = self._rrd.ref_b_poi_ComboBox.itemData(self._rrd.ref_b_poi_ComboBox.currentIndex()) ref_c_key = self._rrd.ref_c_poi_ComboBox.itemData(self._rrd.ref_c_poi_ComboBox.currentIndex()) # Get the old coords for these refs ref_a_coords = np.array(self._poi_manager_logic.poi_list[ref_a_key].get_coords_in_sample()) ref_b_coords = np.array(self._poi_manager_logic.poi_list[ref_b_key].get_coords_in_sample()) ref_c_coords = np.array(self._poi_manager_logic.poi_list[ref_c_key].get_coords_in_sample()) ref_a_newpos = np.array([self._rrd.ref_a_x_pos_DoubleSpinBox.value(), self._rrd.ref_a_y_pos_DoubleSpinBox.value(), self._rrd.ref_a_z_pos_DoubleSpinBox.value()]) ref_b_newpos = np.array([self._rrd.ref_b_x_pos_DoubleSpinBox.value(), self._rrd.ref_b_y_pos_DoubleSpinBox.value(), self._rrd.ref_b_z_pos_DoubleSpinBox.value()]) ref_c_newpos = np.array([self._rrd.ref_c_x_pos_DoubleSpinBox.value(), self._rrd.ref_c_y_pos_DoubleSpinBox.value(), self._rrd.ref_c_z_pos_DoubleSpinBox.value()]) return ref_a_coords, ref_b_coords, ref_c_coords, ref_a_newpos*1e-6, ref_b_newpos*1e-6, ref_c_newpos*1e-6 def reset_reorientation_dialog(self): """ Reset all the values in the reorient roi dialog. """ self._rrd.ref_a_x_pos_DoubleSpinBox.setValue(0) self._rrd.ref_a_y_pos_DoubleSpinBox.setValue(0) self._rrd.ref_a_z_pos_DoubleSpinBox.setValue(0) self._rrd.ref_b_x_pos_DoubleSpinBox.setValue(0) self._rrd.ref_b_y_pos_DoubleSpinBox.setValue(0) self._rrd.ref_b_z_pos_DoubleSpinBox.setValue(0) self._rrd.ref_c_x_pos_DoubleSpinBox.setValue(0) self._rrd.ref_c_y_pos_DoubleSpinBox.setValue(0) self._rrd.ref_c_z_pos_DoubleSpinBox.setValue(0) def reorientation_sanity_check(self): """ Calculate the difference in length between edges of old triangle defined by refs A, B, C and the new triangle. """ # Get set of positions from GUI ref_a_coords, ref_b_coords, ref_c_coords, ref_a_newpos, ref_b_newpos, ref_c_newpos = self._read_reorient_roi_dialog_values() # Calculate the difference in side lengths AB, BC, CA between the old triangle and the new triangle delta_ab = np.linalg.norm(ref_b_coords - ref_a_coords) - np.linalg.norm(ref_b_newpos - ref_a_newpos) delta_bc = np.linalg.norm(ref_c_coords - ref_b_coords) - np.linalg.norm(ref_c_newpos - ref_b_newpos) delta_ca = np.linalg.norm(ref_a_coords - ref_c_coords) - np.linalg.norm(ref_a_newpos - ref_c_newpos) # Write to the GUI self._rrd.length_difference_ab_Label.setText(str(delta_ab)) self._rrd.length_difference_bc_Label.setText(str(delta_bc)) self._rrd.length_difference_ca_Label.setText(str(delta_ca)) def do_autofind_poi_procedure(self): """Run the autofind_pois procedure in the POI Manager Logic to get all the POIs in the current ROI image.""" #Fixme: Add here the appropriate functionality self.log.error("Has to be implemented properly. Feel free to do it.") # # Get the thresholds from the user-chosen color bar range # cb_min, cb_max = self.determine_cb_range() # # this_min_threshold = cb_min + 0.3 * (cb_max - cb_min) # this_max_threshold = cb_max # # self._poi_manager_logic.autofind_pois(neighborhood_size=1, min_threshold=this_min_threshold, max_threshold=this_max_threshold) def optimize_roi(self): """Run the autofind_pois procedure in the POI Manager Logic to get all the POIs in the current ROI image.""" #Fixme: Add here the appropriate functionality self.log.error("Not implemented yet. Feel free to help!")
class VoltScanGui(GUIBase): """ """ _modclass = 'VoltScanGui' _modtype = 'gui' ## declare connectors voltagescannerlogic1 = Connector(interface='VoltageScannerLogic') savelogic = Connector(interface='SaveLogic') sigStartScan = QtCore.Signal() sigStopScan = QtCore.Signal() sigChangeVoltage = QtCore.Signal(float) sigChangeRange = QtCore.Signal(list) sigChangeResolution = QtCore.Signal(float) sigChangeSpeed = QtCore.Signal(float) sigChangeLines = QtCore.Signal(int) sigSaveMeasurement = QtCore.Signal(str, list, list) def on_deactivate(self): """ Reverse steps of activation @return int: error code (0:OK, -1:error) """ self._mw.close() return 0 def on_activate(self): """ """ self._voltscan_logic = self.get_connector('voltagescannerlogic1') self._savelogic = self.get_connector('savelogic') # Use the inherited class 'Ui_VoltagescannerGuiUI' to create now the # GUI element: self._mw = VoltScanMainWindow() # Add save file tag input box self._mw.save_tag_LineEdit = QtWidgets.QLineEdit(self._mw) self._mw.save_tag_LineEdit.setMaximumWidth(500) self._mw.save_tag_LineEdit.setMinimumWidth(200) self._mw.save_tag_LineEdit.setToolTip('Enter a nametag which will be\n' 'added to the filename.') self._mw.toolBar.addWidget(self._mw.save_tag_LineEdit) # Get the image from the logic self.scan_matrix_image = pg.ImageItem( self._voltscan_logic.scan_matrix, axisOrder='row-major') self.scan_matrix_image.setRect( QtCore.QRectF( self._voltscan_logic.scan_range[0], 0, self._voltscan_logic.scan_range[1] - self._voltscan_logic.scan_range[0], self._voltscan_logic.number_of_repeats) ) self.scan_matrix_image2 = pg.ImageItem( self._voltscan_logic.scan_matrix2, axisOrder='row-major') self.scan_matrix_image2.setRect( QtCore.QRectF( self._voltscan_logic.scan_range[0], 0, self._voltscan_logic.scan_range[1] - self._voltscan_logic.scan_range[0], self._voltscan_logic.number_of_repeats) ) self.scan_image = pg.PlotDataItem( self._voltscan_logic.plot_x, self._voltscan_logic.plot_y) self.scan_image2 = pg.PlotDataItem( self._voltscan_logic.plot_x, self._voltscan_logic.plot_y2) self.scan_fit_image = pg.PlotDataItem( self._voltscan_logic.fit_x, self._voltscan_logic.fit_y, pen=QtGui.QPen(QtGui.QColor(255, 255, 255, 255))) # Add the display item to the xy and xz VieWidget, which was defined in # the UI file. self._mw.voltscan_ViewWidget.addItem(self.scan_image) #self._mw.voltscan_ViewWidget.addItem(self.scan_fit_image) self._mw.voltscan_ViewWidget.showGrid(x=True, y=True, alpha=0.8) self._mw.voltscan_matrix_ViewWidget.addItem(self.scan_matrix_image) self._mw.voltscan2_ViewWidget.addItem(self.scan_image2) #self._mw.voltscan2_ViewWidget.addItem(self.scan_fit_image) self._mw.voltscan2_ViewWidget.showGrid(x=True, y=True, alpha=0.8) self._mw.voltscan_matrix2_ViewWidget.addItem(self.scan_matrix_image2) # Get the colorscales at set LUT my_colors = ColorScaleInferno() self.scan_matrix_image.setLookupTable(my_colors.lut) self.scan_matrix_image2.setLookupTable(my_colors.lut) # Configuration of the Colorbar self.scan_cb = ColorBar(my_colors.cmap_normed, 100, 0, 100000) #adding colorbar to ViewWidget self._mw.voltscan_cb_ViewWidget.addItem(self.scan_cb) self._mw.voltscan_cb_ViewWidget.hideAxis('bottom') self._mw.voltscan_cb_ViewWidget.hideAxis('left') self._mw.voltscan_cb_ViewWidget.setLabel('right', 'Fluorescence', units='c/s') # Connect the buttons and inputs for colorbar self._mw.voltscan_cb_manual_RadioButton.clicked.connect(self.refresh_matrix) self._mw.voltscan_cb_centiles_RadioButton.clicked.connect(self.refresh_matrix) # set initial values self._mw.startDoubleSpinBox.setValue(self._voltscan_logic.scan_range[0]) self._mw.speedDoubleSpinBox.setValue(self._voltscan_logic._scan_speed) self._mw.stopDoubleSpinBox.setValue(self._voltscan_logic.scan_range[1]) self._mw.constDoubleSpinBox.setValue(self._voltscan_logic._static_v) self._mw.resolutionSpinBox.setValue(self._voltscan_logic.resolution) self._mw.linesSpinBox.setValue(self._voltscan_logic.number_of_repeats) # Update the inputed/displayed numbers if the cursor has left the field: self._mw.startDoubleSpinBox.editingFinished.connect(self.change_start_volt) self._mw.speedDoubleSpinBox.editingFinished.connect(self.change_speed) self._mw.stopDoubleSpinBox.editingFinished.connect(self.change_stop_volt) self._mw.resolutionSpinBox.editingFinished.connect(self.change_resolution) self._mw.linesSpinBox.editingFinished.connect(self.change_lines) self._mw.constDoubleSpinBox.editingFinished.connect(self.change_voltage) # self._mw.voltscan_cb_max_InputWidget.valueChanged.connect(self.refresh_matrix) self._mw.voltscan_cb_min_InputWidget.valueChanged.connect(self.refresh_matrix) self._mw.voltscan_cb_high_centile_InputWidget.valueChanged.connect(self.refresh_matrix) self._mw.voltscan_cb_low_centile_InputWidget.valueChanged.connect(self.refresh_matrix) # Connect signals self._voltscan_logic.sigUpdatePlots.connect(self.refresh_matrix) self._voltscan_logic.sigUpdatePlots.connect(self.refresh_plot) self._voltscan_logic.sigUpdatePlots.connect(self.refresh_lines) self._voltscan_logic.sigScanFinished.connect(self.scan_stopped) self._voltscan_logic.sigScanStarted.connect(self.scan_started) self.sigStartScan.connect(self._voltscan_logic.start_scanning) self.sigStopScan.connect(self._voltscan_logic.stop_scanning) self.sigChangeVoltage.connect(self._voltscan_logic.set_voltage) self.sigChangeRange.connect(self._voltscan_logic.set_scan_range) self.sigChangeSpeed.connect(self._voltscan_logic.set_scan_speed) self.sigChangeLines.connect(self._voltscan_logic.set_scan_lines) self.sigChangeResolution.connect(self._voltscan_logic.set_resolution) self.sigSaveMeasurement.connect(self._voltscan_logic.save_data) self._mw.action_run_stop.triggered.connect(self.run_stop) self._mw.action_Save.triggered.connect(self.save_data) self._mw.show() def show(self): """Make window visible and put it above all other windows. """ self._mw.show() self._mw.activateWindow() self._mw.raise_() def run_stop(self, is_checked): """ Manages what happens if scan is started/stopped """ self._mw.action_run_stop.setEnabled(False) if is_checked: self.sigStartScan.emit() self._mw.voltscan_ViewWidget.removeItem(self.scan_fit_image) self._mw.voltscan2_ViewWidget.removeItem(self.scan_fit_image) else: self.sigStopScan.emit() def scan_started(self): self._mw.action_run_stop.setEnabled(True) def scan_stopped(self): self._mw.action_run_stop.setEnabled(True) self._mw.action_run_stop.setChecked(False) self.refresh_plot() self.refresh_matrix() self.refresh_lines() def refresh_plot(self): """ Refresh the xy-plot image """ self.scan_image.setData(self._voltscan_logic.plot_x, self._voltscan_logic.plot_y) self.scan_image2.setData(self._voltscan_logic.plot_x, self._voltscan_logic.plot_y2) def refresh_matrix(self): """ Refresh the xy-matrix image """ self.scan_matrix_image.setImage(self._voltscan_logic.scan_matrix, axisOrder='row-major') self.scan_matrix_image.setRect( QtCore.QRectF( self._voltscan_logic.scan_range[0], 0, self._voltscan_logic.scan_range[1] - self._voltscan_logic.scan_range[0], self._voltscan_logic.number_of_repeats) ) self.scan_matrix_image2.setImage(self._voltscan_logic.scan_matrix2, axisOrder='row-major') self.scan_matrix_image2.setRect( QtCore.QRectF( self._voltscan_logic.scan_range[0], 0, self._voltscan_logic.scan_range[1] - self._voltscan_logic.scan_range[0], self._voltscan_logic.number_of_repeats) ) self.refresh_scan_colorbar() scan_image_data = self._voltscan_logic.scan_matrix # If "Centiles" is checked, adjust colour scaling automatically to centiles. # Otherwise, take user-defined values. if self._mw.voltscan_cb_centiles_RadioButton.isChecked(): low_centile = self._mw.voltscan_cb_low_centile_InputWidget.value() high_centile = self._mw.voltscan_cb_high_centile_InputWidget.value() cb_min = np.percentile(scan_image_data, low_centile) cb_max = np.percentile(scan_image_data, high_centile) else: cb_min = self._mw.voltscan_cb_min_InputWidget.value() cb_max = self._mw.voltscan_cb_max_InputWidget.value() # Now update image with new color scale, and update colorbar self.scan_matrix_image.setImage( image=scan_image_data, levels=(cb_min, cb_max), axisOrder='row-major') scan_image_data2 = self._voltscan_logic.scan_matrix2 # Now update image with new color scale, and update colorbar self.scan_matrix_image2.setImage( image=scan_image_data2, levels=(cb_min, cb_max), axisOrder='row-major') self.refresh_scan_colorbar() def refresh_scan_colorbar(self): """ Update the colorbar to a new scaling.""" # If "Centiles" is checked, adjust colour scaling automatically to centiles. # Otherwise, take user-defined values. if self._mw.voltscan_cb_centiles_RadioButton.isChecked(): low_centile = self._mw.voltscan_cb_low_centile_InputWidget.value() high_centile = self._mw.voltscan_cb_high_centile_InputWidget.value() cb_min = np.percentile(self.scan_matrix_image.image, low_centile) cb_max = np.percentile(self.scan_matrix_image.image, high_centile) else: cb_min = self._mw.voltscan_cb_min_InputWidget.value() cb_max = self._mw.voltscan_cb_max_InputWidget.value() self.scan_cb.refresh_colorbar(cb_min, cb_max) self._mw.voltscan_cb_ViewWidget.update() def refresh_lines(self): self._mw.elapsed_lines_DisplayWidget.display(self._voltscan_logic._scan_counter_up) def change_voltage(self): self.sigChangeVoltage.emit(self._mw.constDoubleSpinBox.value()) def change_start_volt(self): self.sigChangeRange.emit([ self._mw.startDoubleSpinBox.value(), self._mw.stopDoubleSpinBox.value() ]) def change_speed(self): self.sigChangeSpeed.emit(self._mw.speedDoubleSpinBox.value()) def change_stop_volt(self): self.sigChangeRange.emit([ self._mw.startDoubleSpinBox.value(), self._mw.stopDoubleSpinBox.value() ]) def change_lines(self): self.sigChangeLines.emit(self._mw.linesSpinBox.value()) def change_resolution(self): self.sigChangeResolution.emit(self._mw.resolutionSpinBox.value()) def get_matrix_cb_range(self): """ Determines the cb_min and cb_max values for the matrix plot """ matrix_image = self.scan_matrix_image.image # If "Manual" is checked or the image is empty (all zeros), then take manual cb range. # Otherwise, calculate cb range from percentiles. if self._mw.voltscan_cb_manual_RadioButton.isChecked() or np.max(matrix_image) < 0.1: cb_min = self._mw.voltscan_cb_min_InputWidget.value() cb_max = self._mw.voltscan_cb_max_InputWidget.value() else: # Exclude any zeros (which are typically due to unfinished scan) matrix_image_nonzero = matrix_image[np.nonzero(matrix_image)] # Read centile range low_centile = self._mw.voltscan_cb_low_centile_InputWidget.value() high_centile = self._mw.voltscan_cb_high_centile_InputWidget.value() cb_min = np.percentile(matrix_image_nonzero, low_centile) cb_max = np.percentile(matrix_image_nonzero, high_centile) cb_range = [cb_min, cb_max] return cb_range def save_data(self): """ Save the sum plot, the scan marix plot and the scan data """ filetag = self._mw.save_tag_LineEdit.text() cb_range = self.get_matrix_cb_range() # Percentile range is None, unless the percentile scaling is selected in GUI. pcile_range = None if self._mw.voltscan_cb_centiles_RadioButton.isChecked(): low_centile = self._mw.voltscan_cb_low_centile_InputWidget.value() high_centile = self._mw.voltscan_cb_high_centile_InputWidget.value() pcile_range = [low_centile, high_centile] self.sigSaveMeasurement.emit(filetag, cb_range, pcile_range)
class ODMRGui(GUIBase): """ This is the GUI Class for ODMR """ _modclass = 'ODMRGui' _modtype = 'gui' # declare connectors _in = {'odmrlogic1': 'ODMRLogic', 'savelogic': 'SaveLogic'} sigStartODMRScan = QtCore.Signal() sigStopODMRScan = QtCore.Signal() sigContinueODMRScan = QtCore.Signal() sigClearPlots = QtCore.Signal() sigMWOn = QtCore.Signal() sigMWOff = QtCore.Signal() sigMWPowerChanged = QtCore.Signal(float) sigMWFreqChanged = QtCore.Signal(float) def __init__(self, config, **kwargs): super().__init__(config=config, **kwargs) self.log.info('The following configuration was found.') # checking for the right configuration for key in config.keys(): self.log.info('{0}: {1}'.format(key, config[key])) def on_activate(self, e=None): """ Definition, configuration and initialisation of the ODMR GUI. @param object e: Fysom.event object from Fysom class. An object created by the state machine module Fysom, which is connected to a specific event (have a look in the Base Class). This object contains the passed event, the state before the event happened and the destination of the state which should be reached after the event had happened. This init connects all the graphic modules, which were created in the *.ui file and configures the event handling between the modules. """ self._odmr_logic = self.get_in_connector('odmrlogic1') self._save_logic = self.get_in_connector('savelogic') # Use the inherited class 'Ui_ODMRGuiUI' to create now the # GUI element: self._mw = ODMRMainWindow() self._sd = ODMRSettingDialog() # Create a QSettings object for the mainwindow and store the actual GUI layout self.mwsettings = QtCore.QSettings("QUDI", "ODMR") self.mwsettings.setValue("geometry", self._mw.saveGeometry()) self.mwsettings.setValue("windowState", self._mw.saveState()) # Adjust range of scientific spinboxes above what is possible in Qt Designer self._mw.frequency_DoubleSpinBox.setMaximum( self._odmr_logic.limits.max_frequency) self._mw.frequency_DoubleSpinBox.setMinimum( self._odmr_logic.limits.min_frequency) self._mw.start_freq_DoubleSpinBox.setMaximum( self._odmr_logic.limits.max_frequency) self._mw.start_freq_DoubleSpinBox.setMinimum( self._odmr_logic.limits.min_frequency) self._mw.step_freq_DoubleSpinBox.setMaximum(100e9) self._mw.stop_freq_DoubleSpinBox.setMaximum( self._odmr_logic.limits.max_frequency) self._mw.stop_freq_DoubleSpinBox.setMinimum( self._odmr_logic.limits.min_frequency) self._mw.power_DoubleSpinBox.setMaximum( self._odmr_logic.limits.max_power) self._mw.power_DoubleSpinBox.setMinimum( self._odmr_logic.limits.min_power) # Add save file tag input box self._mw.save_tag_LineEdit = QtWidgets.QLineEdit(self._mw) self._mw.save_tag_LineEdit.setMaximumWidth(200) self._mw.save_tag_LineEdit.setToolTip( 'Enter a nametag which will be\nadded to the filename.') self._mw.save_ToolBar.addWidget(self._mw.save_tag_LineEdit) # add a clear button to clear the ODMR plots: self._mw.clear_odmr_PushButton = QtWidgets.QPushButton(self._mw) self._mw.clear_odmr_PushButton.setText('Clear ODMR') self._mw.clear_odmr_PushButton.setToolTip( 'Clear the plots of the\ncurrent ODMR measurements.') self._mw.clear_odmr_PushButton.setEnabled(False) self._mw.save_ToolBar.addWidget(self._mw.clear_odmr_PushButton) self.sigClearPlots.connect(self._odmr_logic.clear_odmr_plots) # Get the image from the logic self.odmr_matrix_image = pg.ImageItem( self._odmr_logic.ODMR_plot_xy.transpose()) self.odmr_matrix_image.setRect( QtCore.QRectF(self._odmr_logic.mw_start, 0, self._odmr_logic.mw_stop - self._odmr_logic.mw_start, self._odmr_logic.number_of_lines)) self.odmr_image = pg.PlotDataItem(self._odmr_logic.ODMR_plot_x, self._odmr_logic.ODMR_plot_y, pen=pg.mkPen( palette.c1, style=QtCore.Qt.DotLine), symbol='o', symbolPen=palette.c1, symbolBrush=palette.c1, symbolSize=7) self.odmr_fit_image = pg.PlotDataItem(self._odmr_logic.ODMR_fit_x, self._odmr_logic.ODMR_fit_y, pen=pg.mkPen(palette.c2)) # Add the display item to the xy and xz VieWidget, which was defined in # the UI file. self._mw.odmr_PlotWidget.addItem(self.odmr_image) self._mw.odmr_PlotWidget.setLabel(axis='left', text='Counts', units='Counts/s') self._mw.odmr_PlotWidget.setLabel(axis='bottom', text='Frequency', units='Hz') self._mw.odmr_matrix_PlotWidget.addItem(self.odmr_matrix_image) self._mw.odmr_matrix_PlotWidget.setLabel(axis='left', text='Matrix Lines', units='#') self._mw.odmr_matrix_PlotWidget.setLabel(axis='bottom', text='Frequency', units='Hz') self._mw.odmr_PlotWidget.showGrid(x=True, y=True, alpha=0.8) # Get the colorscales at set LUT my_colors = ColorScaleInferno() self.odmr_matrix_image.setLookupTable(my_colors.lut) # Configuration of the comboWidget self._mw.mode_ComboBox.addItem('Off') self._mw.mode_ComboBox.addItem('CW') fit_functions = self._odmr_logic.get_fit_functions() self._mw.fit_methods_ComboBox.clear() self._mw.fit_methods_ComboBox.addItems(fit_functions) ######################################################################## # Configuration of the Colorbar # ######################################################################## self.odmr_cb = ColorBar(my_colors.cmap_normed, 100, 0, 100000) # adding colorbar to ViewWidget self._mw.odmr_cb_PlotWidget.addItem(self.odmr_cb) self._mw.odmr_cb_PlotWidget.hideAxis('bottom') self._mw.odmr_cb_PlotWidget.hideAxis('left') self._mw.odmr_cb_PlotWidget.setLabel('right', 'Fluorescence', units='counts/s') # Connect the buttons and inputs for the odmr colorbar self._mw.odmr_cb_manual_RadioButton.clicked.connect( self.refresh_matrix) self._mw.odmr_cb_centiles_RadioButton.clicked.connect( self.refresh_matrix) ######################################################################## # Configuration of the various display Widgets # ######################################################################## # Take the default values from logic: self._mw.frequency_DoubleSpinBox.setValue( self._odmr_logic.mw_frequency) self._mw.start_freq_DoubleSpinBox.setValue(self._odmr_logic.mw_start) self._mw.step_freq_DoubleSpinBox.setValue(self._odmr_logic.mw_step) self._mw.step_freq_DoubleSpinBox.setOpts( minStep=1.0) # set the minimal step to 1Hz. self._mw.stop_freq_DoubleSpinBox.setValue(self._odmr_logic.mw_stop) self._mw.power_DoubleSpinBox.setValue(self._odmr_logic.mw_power) self._mw.power_DoubleSpinBox.setOpts(minStep=0.1) self._mw.runtime_DoubleSpinBox.setValue(self._odmr_logic.run_time) self._mw.elapsed_time_DisplayWidget.display( int(self._odmr_logic.elapsed_time)) self._sd.matrix_lines_SpinBox.setValue( self._odmr_logic.number_of_lines) self._sd.clock_frequency_DoubleSpinBox.setValue( self._odmr_logic._clock_frequency) self._sd.fit_tabs = {} for name, model in self._odmr_logic.fit_models.items(): try: self._sd.fit_tabs[name] = FitSettingsWidget(model[1]) except: self.log.warning('Could not load fitmodel {0}'.format(name)) else: self._sd.tabWidget.addTab(self._sd.fit_tabs[name], name) # Update the inputed/displayed numbers if return key is hit: # If the attribute setKeyboardTracking is set in a SpinBox or # DoubleSpinBox the valueChanged method will actually hold on the signal # until the return key is pressed which is pretty useful ;) # self._mw.frequency_DoubleSpinBox.setKeyboardTracking(False) # Update the inputed/displayed numbers if the cursor has left the field: self._mw.frequency_DoubleSpinBox.editingFinished.connect( self.change_frequency) self._mw.start_freq_DoubleSpinBox.editingFinished.connect( self.change_start_freq) self._mw.step_freq_DoubleSpinBox.editingFinished.connect( self.change_step_freq) self._mw.stop_freq_DoubleSpinBox.editingFinished.connect( self.change_stop_freq) self._mw.power_DoubleSpinBox.editingFinished.connect(self.change_power) self._mw.runtime_DoubleSpinBox.editingFinished.connect( self.change_runtime) self._mw.odmr_cb_max_DoubleSpinBox.valueChanged.connect( self.refresh_matrix) self._mw.odmr_cb_min_DoubleSpinBox.valueChanged.connect( self.refresh_matrix) self._mw.odmr_cb_high_percentile_DoubleSpinBox.valueChanged.connect( self.refresh_matrix) self._mw.odmr_cb_low_percentile_DoubleSpinBox.valueChanged.connect( self.refresh_matrix) ######################################################################## # Connect signals # ######################################################################## # Connect the RadioButtons and connect to the events if they are clicked: self._mw.action_run_stop.triggered.connect(self.run_stop) self._mw.action_resume_odmr.triggered.connect(self.resume_odmr) self._mw.action_Save.triggered.connect(self.save_data) self._mw.action_RestoreDefault.triggered.connect( self.restore_defaultview) self.sigStartODMRScan.connect(self._odmr_logic.start_odmr_scan) self.sigStopODMRScan.connect(self._odmr_logic.stop_odmr_scan) self.sigContinueODMRScan.connect(self._odmr_logic.continue_odmr_scan) self.sigMWOn.connect(self._odmr_logic.MW_on) self.sigMWOff.connect(self._odmr_logic.MW_off) self.sigMWFreqChanged.connect(self._odmr_logic.set_frequency) self.sigMWPowerChanged.connect(self._odmr_logic.set_power) # react on an axis change in the logic by adapting the display: self._odmr_logic.sigODMRMatrixAxesChanged.connect( self.update_matrix_axes) # connect the clear button: self._mw.clear_odmr_PushButton.clicked.connect( self.clear_odmr_plots_clicked) self._odmr_logic.sigOdmrPlotUpdated.connect(self.refresh_plot) self._odmr_logic.sigOdmrFitUpdated.connect(self.refresh_plot_fit) self._odmr_logic.sigOdmrMatrixUpdated.connect(self.refresh_matrix) self._odmr_logic.sigOdmrElapsedTimeChanged.connect( self.refresh_elapsedtime) # connect settings signals self._mw.action_Settings.triggered.connect(self.menu_settings) self._sd.accepted.connect(self.update_settings) self._sd.rejected.connect(self.reject_settings) self._sd.buttonBox.button( QtWidgets.QDialogButtonBox.Apply).clicked.connect( self.update_settings) self.reject_settings() # Connect stop odmr self._odmr_logic.sigOdmrStarted.connect(self.odmr_started) self._odmr_logic.sigOdmrStopped.connect(self.odmr_stopped) # Combo Widget self._mw.mode_ComboBox.activated[str].connect(self.mw_stop) self._mw.fit_methods_ComboBox.activated[str].connect( self.update_fit_variable) # Push Buttons self._mw.do_fit_PushButton.clicked.connect(self.update_fit) # let the gui react on the signals from the GUI self._odmr_logic.sigMicrowaveCWModeChanged.connect( self.update_cw_display) self._odmr_logic.sigMicrowaveListModeChanged.connect( self.update_run_stop_display) # Show the Main ODMR GUI: self._mw.show() def on_deactivate(self, e): """ Reverse steps of activation @param object e: Fysom.event object from Fysom class. A more detailed explanation can be found in the method initUI. @return int: error code (0:OK, -1:error) """ self._mw.close() return 0 def show(self): """Make window visible and put it above all other windows. """ QtWidgets.QMainWindow.show(self._mw) self._mw.activateWindow() self._mw.raise_() def run_stop(self, is_checked): """ Manages what happens if odmr scan is started/stopped. """ if is_checked: # change the axes appearance according to input values: self.sigStopODMRScan.emit() self.sigStartODMRScan.emit() self._mw.action_run_stop.setEnabled(False) self._mw.action_resume_odmr.setEnabled(False) self._mw.odmr_PlotWidget.removeItem(self.odmr_fit_image) # during scan, enable the clear plot possibility. self._mw.clear_odmr_PushButton.setEnabled(True) else: self.sigStopODMRScan.emit() self._mw.action_run_stop.setEnabled(False) self._mw.action_resume_odmr.setEnabled(False) # Disable the clear functionality since that is not needed if no # scan is running: self._mw.clear_odmr_PushButton.setEnabled(False) def update_cw_display(self, cw_on): """ Update the display for the cw state of the microwave. @param bool cw_on: for True the mw on display will be shown, otherwise mw off will be displayed. """ if cw_on: # # prevent any triggering, which results from changing the state of # # the combobox: # self._mw.mode_ComboBox.blockSignals(True) text = 'CW' else: text = 'Off' index = self._mw.mode_ComboBox.findText(text, QtCore.Qt.MatchFixedString) if index >= 0: self._mw.mode_ComboBox.setCurrentIndex(index) else: self.log.warning( 'No proper state to display was found in the combobox!') def update_run_stop_display(self, run_odmr): """ Update the display for the odmr measurement. @param bool run_odmr: True indicates that the measurement is running and False that it is stopped. """ if run_odmr: self._mw.action_resume_odmr.setEnabled(False) self._mw.clear_odmr_PushButton.setEnabled(True) else: self._mw.action_resume_odmr.setEnabled(True) self._mw.clear_odmr_PushButton.setEnabled(False) def resume_odmr(self, is_checked): if is_checked: self.sigStopODMRScan.emit() self.sigContinueODMRScan.emit() self._mw.action_run_stop.setChecked(True) self._mw.action_run_stop.setEnabled(False) self._mw.action_resume_odmr.setEnabled(False) # during scan, enable the clear plot possibility. self._mw.clear_odmr_PushButton.setEnabled(True) else: self.sigStopODMRScan.emit() self._mw.action_run_stop.setChecked(False) self._mw.action_run_stop.setEnabled(True) self._mw.action_resume_odmr.setEnabled(True) # Disable the clear functionality since that is not needed if no scan is running: self._mw.clear_odmr_PushButton.setEnabled(False) def odmr_started(self): """ Switch the run/stop button to stop after receiving an odmsStarted signal """ self._mw.action_run_stop.setEnabled(True) self._mw.action_resume_odmr.setEnabled(False) def odmr_stopped(self): """ Switch the run/stop button to stop after receiving an odmr_stoped signal """ self._mw.action_run_stop.setChecked(False) self._mw.action_resume_odmr.setChecked(False) self._mw.action_run_stop.setEnabled(True) self._mw.action_resume_odmr.setEnabled(True) def clear_odmr_plots_clicked(self): """ Clear the ODMR plots. """ self.sigClearPlots.emit() def menu_settings(self): """ Open the settings menu """ self._sd.exec_() def refresh_plot(self): """ Refresh the xy-plot image """ self.odmr_image.setData(self._odmr_logic.ODMR_plot_x, self._odmr_logic.ODMR_plot_y) def refresh_plot_fit(self): """ Refresh the xy fit plot image. """ if not self._mw.fit_methods_ComboBox.currentText() == 'No Fit': self.odmr_fit_image.setData(x=self._odmr_logic.ODMR_fit_x, y=self._odmr_logic.ODMR_fit_y) else: if self.odmr_fit_image in self._mw.odmr_PlotWidget.listDataItems(): self._mw.odmr_PlotWidget.removeItem(self.odmr_fit_image) def refresh_matrix(self): """ Refresh the xy-matrix image """ odmr_image_data = self._odmr_logic.ODMR_plot_xy.transpose() cb_range = self.get_matrix_cb_range() # Now update image with new color scale, and update colorbar self.odmr_matrix_image.setImage(image=odmr_image_data, levels=(cb_range[0], cb_range[1])) self.refresh_odmr_colorbar() def update_matrix_axes(self): """ Adjust the x and y axes in the image according to the input. """ self.odmr_matrix_image.setRect( QtCore.QRectF(self._odmr_logic.mw_start, 0, self._odmr_logic.mw_stop - self._odmr_logic.mw_start, self._odmr_logic.number_of_lines)) def refresh_odmr_colorbar(self): """ Update the colorbar to a new scaling. Calls the refresh method from colorbar. """ cb_range = self.get_matrix_cb_range() self.odmr_cb.refresh_colorbar(cb_range[0], cb_range[1]) self._mw.odmr_cb_PlotWidget.update( ) # TODO: Is this necessary? It is not in refresh_xy_colorbar in confocal gui def get_matrix_cb_range(self): """ Determines the cb_min and cb_max values for the matrix plot """ matrix_image = self.odmr_matrix_image.image # If "Manual" is checked or the image is empty (all zeros), then take manual cb range. if self._mw.odmr_cb_manual_RadioButton.isChecked() or np.max( matrix_image) == 0.0: cb_min = self._mw.odmr_cb_min_DoubleSpinBox.value() cb_max = self._mw.odmr_cb_max_DoubleSpinBox.value() # Otherwise, calculate cb range from percentiles. else: # Exclude any zeros (which are typically due to unfinished scan) matrix_image_nonzero = matrix_image[np.nonzero(matrix_image)] # Read centile range low_centile = self._mw.odmr_cb_low_percentile_DoubleSpinBox.value() high_centile = self._mw.odmr_cb_high_percentile_DoubleSpinBox.value( ) cb_min = np.percentile(matrix_image_nonzero, low_centile) cb_max = np.percentile(matrix_image_nonzero, high_centile) cb_range = [cb_min, cb_max] return cb_range def refresh_elapsedtime(self): """ Show current elapsed measurement time """ self._mw.elapsed_time_DisplayWidget.display( int(self._odmr_logic.elapsed_time)) def update_settings(self): """ Write the new settings from the gui to the file. """ self._odmr_logic.number_of_lines = self._sd.matrix_lines_SpinBox.value( ) self._odmr_logic.set_clock_frequency( self._sd.clock_frequency_DoubleSpinBox.value()) self._odmr_logic.saveRawData = self._sd.save_raw_data_CheckBox.isChecked( ) for name, tab in self._sd.fit_tabs.items(): self._odmr_logic.use_custom_params[name] = tab.updateFitSettings( self._odmr_logic.fit_models[name][1]) def reject_settings(self): """ Keep the old settings and restores the old settings in the gui. """ self._sd.matrix_lines_SpinBox.setValue( self._odmr_logic.number_of_lines) self._sd.clock_frequency_DoubleSpinBox.setValue( self._odmr_logic._clock_frequency) self._sd.save_raw_data_CheckBox.setChecked( self._odmr_logic.saveRawData) for name, tab in self._sd.fit_tabs.items(): tab.keepFitSettings(self._odmr_logic.fit_models[name][1], self._odmr_logic.use_custom_params[name]) def update_fit_variable(self, txt): """ Set current fit function """ self._odmr_logic.current_fit_function = txt def update_fit(self): """ Do the configured fit and show it in the sum plot """ x_data_fit, y_data_fit, fit_param, fit_result = self._odmr_logic.do_fit( fit_function=self._odmr_logic.current_fit_function) self._sd.fit_tabs[ self._odmr_logic.current_fit_function].keepFitSettings( fit_result.params, 0) # The fit signal was already emitted in the logic, so there is no need # to set the fit data # check which Fit method is used and remove or add again the # odmr_fit_image, check also whether a odmr_fit_image already exists. if self._mw.fit_methods_ComboBox.currentText() == 'No Fit': if self.odmr_fit_image in self._mw.odmr_PlotWidget.listDataItems(): self._mw.odmr_PlotWidget.removeItem(self.odmr_fit_image) else: if self.odmr_fit_image not in self._mw.odmr_PlotWidget.listDataItems( ): self._mw.odmr_PlotWidget.addItem(self.odmr_fit_image) self._mw.odmr_PlotWidget.getViewBox().updateAutoRange() self._mw.odmr_fit_results_DisplayWidget.clear() formated_results = units.create_formatted_output(fit_param) self._mw.odmr_fit_results_DisplayWidget.setPlainText(formated_results) def _format_param_dict(self, param_dict): """ Create from the passed param_dict a proper display of the parameters. @param dict param_dict: the dictionary with keys being the names of the parameter and items being values/parameters. @return: """ pass def mw_stop(self, txt): """ Stop frequency sweep and change to CW of off""" if txt == 'Off': self.sigMWOff.emit() if txt == 'CW': self.change_frequency() self.change_power() self.sigMWOn.emit() ############################################################################ # Change Methods # ############################################################################ def change_frequency(self): """ Change CW frequency of microwave source """ frequency = self._mw.frequency_DoubleSpinBox.value() self.sigMWFreqChanged.emit(frequency) def change_start_freq(self): """ Change start frequency of frequency sweep """ self._odmr_logic.mw_start = self._mw.start_freq_DoubleSpinBox.value() def change_step_freq(self): """ Change step size in which frequency is changed """ self._odmr_logic.mw_step = self._mw.step_freq_DoubleSpinBox.value() def change_stop_freq(self): """ Change end of frequency sweep """ self._odmr_logic.mw_stop = self._mw.stop_freq_DoubleSpinBox.value() def change_power(self): """ Change microwave power """ power = self._mw.power_DoubleSpinBox.value() self.sigMWPowerChanged.emit(power) def change_runtime(self): """ Change time after which microwave sweep is stopped """ self._odmr_logic.run_time = self._mw.runtime_DoubleSpinBox.value() def save_data(self): """ Save the sum plot, the scan marix plot and the scan data """ filetag = self._mw.save_tag_LineEdit.text() cb_range = self.get_matrix_cb_range() # Percentile range is None, unless the percentile scaling is selected in GUI. pcile_range = None if self._mw.odmr_cb_centiles_RadioButton.isChecked(): low_centile = self._mw.odmr_cb_low_percentile_DoubleSpinBox.value() high_centile = self._mw.odmr_cb_high_percentile_DoubleSpinBox.value( ) pcile_range = [low_centile, high_centile] self._odmr_logic.save_ODMR_Data(filetag, colorscale_range=cb_range, percentile_range=pcile_range) def restore_defaultview(self): self._mw.restoreGeometry(self.mwsettings.value("geometry", "")) self._mw.restoreState(self.mwsettings.value("windowState", ""))
class VoltScanGui(GUIBase): """ This is the GUI Class for ODMR """ _modclass = 'VoltScanGui' _modtype = 'gui' ## declare connectors _connectors = {'voltagescannerlogic1': 'VoltageScannerLogic', } def __init__(self, config, **kwargs): super().__init__(config=config, **kwargs) self.log.info('The following configuration was found.') # checking for the right configuration for key in config.keys(): self.log.info('{0}: {1}'.format(key,config[key])) def on_deactivate(self): """ Reverse steps of activation @return int: error code (0:OK, -1:error) """ self._mw.close() return 0 def on_activate(self): """ Definition, configuration and initialisation of the ODMR GUI. This init connects all the graphic modules, which were created in the *.ui file and configures the event handling between the modules. """ self._voltscan_logic = self.get_connector('odmrlogic1') print("ODMR logic is", self._odmr_logic) # Use the inherited class 'Ui_VoltagescannerGuiUI' to create now the # GUI element: self._mw = VoltScanMainWindow() # Get the image from the logic self.odmr_matrix_image = pg.ImageItem(self._odmr_logic.ODMR_plot_xy.transpose()) self.odmr_matrix_image.setRect(QtCore.QRectF(self._odmr_logic.mw_start,0,self._odmr_logic.mw_stop-self._odmr_logic.mw_start,self._odmr_logic.number_of_lines)) self.odmr_image = pg.PlotDataItem(self._odmr_logic.ODMR_plot_x,self._odmr_logic.ODMR_plot_y) self.odmr_fit_image = pg.PlotDataItem(self._odmr_logic.ODMR_fit_x,self._odmr_logic.ODMR_fit_y, pen=QtGui.QPen(QtGui.QColor(255,255,255,255))) # Add the display item to the xy and xz VieWidget, which was defined in # the UI file. self._mw.voltscan_ViewWidget.addItem(self.odmr_image) self._mw.voltscan_ViewWidget.addItem(self.odmr_fit_image) self._mw.voltscan_matrix_ViewWidget.addItem(self.odmr_matrix_image) self._mw.vonsoltscan_ViewWidget.showGrid(x=True, y=True, alpha=0.8) # Get the colorscales at set LUT my_colors = ColorScaleInferno() self.odmr_matrix_image.setLookupTable(my_colors.lut) # Set the state button as ready button as default setting. # self._mw.idle_StateWidget.click() # Configuration of the comboWidget self._mw.mode_ComboWidget.addItem('Off') self._mw.mode_ComboWidget.addItem('CW') self._mw.fit_methods_ComboWidget.addItem('No Fit') self._mw.fit_methods_ComboWidget.addItem('Lorentzian') self._mw.fit_methods_ComboWidget.addItem('Double Lorentzian') self._mw.fit_methods_ComboWidget.addItem('Double Lorentzian with fixed splitting') self._mw.fit_methods_ComboWidget.addItem('N14') self._mw.fit_methods_ComboWidget.addItem('N15') ####################################################################### ## Configuration of the Colorbar ## ####################################################################### self.odmr_cb = ColorBar(my_colors.cmap_normed, 100, 0, 100000) #adding colorbar to ViewWidget self._mw.odmr_cb_ViewWidget.addItem(self.odmr_cb) self._mw.odmr_cb_ViewWidget.hideAxis('bottom') self._mw.odmr_cb_ViewWidget.hideAxis('left') self._mw.odmr_cb_ViewWidget.setLabel('right', 'Fluorescence', units='c/s') # Connect the buttons and inputs for the odmr colorbar self._mw.odmr_cb_manual_RadioButton.clicked.connect(self.refresh_matrix) self._mw.odmr_cb_centiles_RadioButton.clicked.connect(self.refresh_matrix) ####################################################################### ## Configuration of the InputWidgets ## ####################################################################### # Add Validators to InputWidgets validator = QtGui.QDoubleValidator() validator2 = QtGui.QIntValidator() self._mw.frequency_InputWidget.setValidator(validator) self._mw.start_freq_InputWidget.setValidator(validator) self._mw.step_freq_InputWidget.setValidator(validator) self._mw.stop_freq_InputWidget.setValidator(validator) self._mw.power_InputWidget.setValidator(validator) self._mw.runtime_InputWidget.setValidator(validator2) self._sd.matrix_lines_InputWidget.setValidator(validator) self._sd.clock_frequency_InputWidget.setValidator(validator2) # Take the default values from logic: self._mw.frequency_InputWidget.setText(str(self._odmr_logic.mw_frequency)) self._mw.start_freq_InputWidget.setText(str(self._odmr_logic.mw_start)) self._mw.step_freq_InputWidget.setText(str(self._odmr_logic.mw_step)) self._mw.stop_freq_InputWidget.setText(str(self._odmr_logic.mw_stop)) self._mw.power_InputWidget.setText(str(self._odmr_logic.mw_power)) self._mw.runtime_InputWidget.setText(str(self._odmr_logic.run_time)) self._mw.elapsed_time_DisplayWidget.display(int(self._odmr_logic.ElapsedTime)) self._sd.matrix_lines_InputWidget.setText(str(self._odmr_logic.number_of_lines)) self._sd.clock_frequency_InputWidget.setText(str(self._odmr_logic._clock_frequency)) # Update the inputed/displayed numbers if return key is hit: self._mw.frequency_InputWidget.returnPressed.connect(self.change_frequency) self._mw.start_freq_InputWidget.returnPressed.connect(self.change_start_freq) self._mw.step_freq_InputWidget.returnPressed.connect(self.change_step_freq) self._mw.stop_freq_InputWidget.returnPressed.connect(self.change_stop_freq) self._mw.power_InputWidget.returnPressed.connect(self.change_power) self._mw.runtime_InputWidget.returnPressed.connect(self.change_runtime) # Update the inputed/displayed numbers if the cursor has left the field: self._mw.frequency_InputWidget.editingFinished.connect(self.change_frequency) self._mw.start_freq_InputWidget.editingFinished.connect(self.change_start_freq) self._mw.step_freq_InputWidget.editingFinished.connect(self.change_step_freq) self._mw.stop_freq_InputWidget.editingFinished.connect(self.change_stop_freq) self._mw.power_InputWidget.editingFinished.connect(self.change_power) self._mw.runtime_InputWidget.editingFinished.connect(self.change_runtime) # self._mw.odmr_cb_max_InputWidget.valueChanged.connect(self.refresh_matrix) self._mw.odmr_cb_min_InputWidget.valueChanged.connect(self.refresh_matrix) self._mw.odmr_cb_high_centile_InputWidget.valueChanged.connect(self.refresh_matrix) self._mw.odmr_cb_low_centile_InputWidget.valueChanged.connect(self.refresh_matrix) ####################################################################### ## Connect signals ## ####################################################################### # Connect the RadioButtons and connect to the events if they are clicked: # self._mw.idle_StateWidget.toggled.connect(self.idle_clicked) # self._mw.run_StateWidget.toggled.connect(self.run_clicked) self._mw.action_run_stop.toggled.connect(self.run_stop) self._mw.action_Save.triggered.connect(self._odmr_logic.save_ODMR_Data) self._odmr_logic.sigOdmrPlotUpdated.connect(self.refresh_plot) self._odmr_logic.sigOdmrPlotUpdated.connect(self.refresh_matrix) self._odmr_logic.sigOdmrElapsedTimeChanged.connect(self.refresh_elapsedtime) # connect settings signals self._mw.action_Settings.triggered.connect(self.menue_settings) self._sd.accepted.connect(self.update_settings) self._sd.rejected.connect(self.reject_settings) self._sd.buttonBox.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect(self.update_settings) self.reject_settings() # Connect stop odmr # self._odmr_logic.sigOdmrFinished.connect(self._mw.idle_StateWidget.click) self._odmr_logic.sigOdmrFinished.connect(self.odmr_stopped) # Combo Widget self._mw.mode_ComboWidget.activated[str].connect(self.mw_stop) self._mw.fit_methods_ComboWidget.activated[str].connect(self.update_fit_variable) # Push Buttons self._mw.do_fit_PushButton.clicked.connect(self.update_fit) # Show the Main ODMR GUI: self._mw.show() def show(self): """Make window visible and put it above all other windows. """ QtWidgets.QMainWindow.show(self._mw) self._mw.activateWindow() self._mw.raise_() # def idle_clicked(self): # """ Stopp the scan if the state has switched to idle. """ # self._odmr_logic.stop_odmr_scan() # self._sd.matrix_lines_InputWidget.setReadOnly(False) # # self._odmr_logic.kill_odmr() # # # def run_clicked(self, enabled): # """ Manages what happens if odmr scan is started. # # @param bool enabled: start scan if that is possible # """ # # #Firstly stop any scan that might be in progress # self._odmr_logic.stop_odmr_scan() # # self._odmr_logic.kill_odmr() # #Then if enabled. start a new odmr scan. # if enabled: # self._odmr_logic.start_odmr_scan() # self._sd.matrix_lines_InputWidget.setReadOnly(True) def run_stop(self, is_checked): """ Manages what happens if odmr scan is started/stopped """ if is_checked: self._odmr_logic.stop_odmr_scan() self._odmr_logic.start_odmr_scan() self._mw.odmr_ViewWidget.removeItem(self.odmr_fit_image) self._sd.matrix_lines_InputWidget.setReadOnly(True) else: self._odmr_logic.stop_odmr_scan() self._sd.matrix_lines_InputWidget.setReadOnly(False) def odmr_stopped(self): """ Switch the run/stop button to stop after receiving an odmr_stoped signal """ self._mw.action_run_stop.setChecked(False) def menue_settings(self): """ Open the settings menue """ self._sd.exec_() def refresh_plot(self): """ Refresh the xy-plot image """ self.odmr_image.setData(self._odmr_logic.ODMR_plot_x,self._odmr_logic.ODMR_plot_y) if not self._mw.fit_methods_ComboWidget.currentText() == 'No Fit': self.odmr_fit_image.setData(self._odmr_logic.ODMR_fit_x,self._odmr_logic.ODMR_fit_y,pen=QtGui.QPen(QtGui.QColor(255,0,255,255))) else: if self.odmr_fit_image in self._mw.odmr_ViewWidget.listDataItems(): self._mw.odmr_ViewWidget.removeItem(self.odmr_fit_image) def refresh_matrix(self): """ Refresh the xy-matrix image """ # self.odmr_matrix_image.setImage(self._odmr_logic.ODMR_plot_xy.transpose()) # self.odmr_matrix_image.setRect(QtCore.QRectF(self._odmr_logic.mw_start,0,self._odmr_logic.mw_stop-self._odmr_logic.mw_start,self._odmr_logic.number_of_lines)) # self.refresh_odmr_colorbar() odmr_image_data = self._odmr_logic.ODMR_plot_xy.transpose() # If "Centiles" is checked, adjust colour scaling automatically to centiles. # Otherwise, take user-defined values. if self._mw.odmr_cb_centiles_RadioButton.isChecked(): low_centile = self._mw.odmr_cb_low_centile_InputWidget.value() high_centile = self._mw.odmr_cb_high_centile_InputWidget.value() cb_min = np.percentile( odmr_image_data, low_centile ) cb_max = np.percentile( odmr_image_data, high_centile ) else: cb_min = self._mw.odmr_cb_min_InputWidget.value() cb_max = self._mw.odmr_cb_max_InputWidget.value() # Now update image with new color scale, and update colorbar self.odmr_matrix_image.setImage(image=odmr_image_data, levels=(cb_min, cb_max) ) self.odmr_matrix_image.setRect(QtCore.QRectF(self._odmr_logic.mw_start,0,self._odmr_logic.mw_stop-self._odmr_logic.mw_start,self._odmr_logic.number_of_lines)) self.refresh_odmr_colorbar() def refresh_odmr_colorbar(self): """ Update the colorbar to a new scaling.""" # If "Centiles" is checked, adjust colour scaling automatically to centiles. # Otherwise, take user-defined values. if self._mw.odmr_cb_centiles_RadioButton.isChecked(): low_centile = self._mw.odmr_cb_low_centile_InputWidget.value() high_centile = self._mw.odmr_cb_high_centile_InputWidget.value() cb_min = np.percentile( self.odmr_matrix_image.image, low_centile ) cb_max = np.percentile( self.odmr_matrix_image.image, high_centile ) else: cb_min = self._mw.odmr_cb_min_InputWidget.value() cb_max = self._mw.odmr_cb_max_InputWidget.value() self.odmr_cb.refresh_colorbar(cb_min,cb_max) self._mw.odmr_cb_ViewWidget.update() def refresh_elapsedtime(self): self._mw.elapsed_time_DisplayWidget.display(int(self._odmr_logic.ElapsedTime)) def update_settings(self): """ Write the new settings from the gui to the file. """ self._odmr_logic.number_of_lines = int(self._sd.matrix_lines_InputWidget.text()) self._odmr_logic.set_clock_frequency(int(self._sd.clock_frequency_InputWidget.text())) self._odmr_logic.safeRawData = self._sd.save_raw_data_box.isChecked() def update_fit_variable(self, txt): self._odmr_logic.current_fit_function = txt def update_fit(self): self._odmr_logic.do_fit(fit_function = self._odmr_logic.current_fit_function) self.refresh_plot() # check which Fit method is used and remove or add again the # odmr_fit_image, check also whether a odmr_fit_image already exists. if self._mw.fit_methods_ComboWidget.currentText() == 'No Fit': if self.odmr_fit_image in self._mw.odmr_ViewWidget.listDataItems(): self._mw.odmr_ViewWidget.removeItem(self.odmr_fit_image) else: if self.odmr_fit_image not in self._mw.odmr_ViewWidget.listDataItems(): self._mw.odmr_ViewWidget.addItem(self.odmr_fit_image) self._mw.odmr_ViewWidget.getViewBox().updateAutoRange() self._mw.odmr_fit_results_DisplayWidget.clear() self._mw.odmr_fit_results_DisplayWidget.setPlainText(str(self._odmr_logic.fit_result)) def reject_settings(self): """ Keep the old settings and restores the old settings in the gui. """ self._sd.matrix_lines_InputWidget.setText(str(self._odmr_logic.number_of_lines)) self._sd.clock_frequency_InputWidget.setText(str(self._odmr_logic._clock_frequency)) self._sd.save_raw_data_box.setChecked(self._odmr_logic.safeRawData) def mw_stop(self, txt): if txt == 'Off': self._odmr_logic.MW_off() if txt == 'CW': self.change_frequency() self.change_power() self._odmr_logic.MW_on() ########################################################################### ## Change Methods ## ########################################################################### def change_frequency(self): self._odmr_logic.set_frequency(frequency = float(self._mw.frequency_InputWidget.text())) def change_start_freq(self): self._odmr_logic.mw_start = float(self._mw.start_freq_InputWidget.text()) def change_step_freq(self): self._odmr_logic.mw_step = float(self._mw.step_freq_InputWidget.text()) def change_stop_freq(self): self._odmr_logic.mw_stop = float(self._mw.stop_freq_InputWidget.text()) def change_power(self): self._odmr_logic.mw_power = float(self._mw.power_InputWidget.text()) self._odmr_logic.set_power(power = self._odmr_logic.mw_power) def change_runtime(self): self._odmr_logic.run_time = float(self._mw.runtime_InputWidget.text())
class PoiManagerGui(GUIBase): """ This is the GUI Class for PoiManager """ _modclass = 'PoiManagerGui' _modtype = 'gui' # declare connectors poimanagerlogic = Connector(interface='PoiManagerLogic') scannerlogic = Connector(interface='ConfocalLogic') # declare signals sigTrackPeriodChanged = QtCore.Signal(float) sigPoiNameChanged = QtCore.Signal(str) sigPoiNameTagChanged = QtCore.Signal(str) sigRoiNameChanged = QtCore.Signal(str) sigAddPoiByClick = QtCore.Signal(np.ndarray) def __init__(self, config, **kwargs): super().__init__(config=config, **kwargs) self._mw = None # QMainWindow handle self.roi_image = None # pyqtgraph PlotImage for ROI scan image self.roi_cb = None # The qudi colorbar to use with roi_image self.x_shift_plot = None # pyqtgraph PlotDataItem for ROI history plot self.y_shift_plot = None # pyqtgraph PlotDataItem for ROI history plot self.z_shift_plot = None # pyqtgraph PlotDataItem for ROI history plot self._markers = dict() # dict to hold handles for the POI markers self._mouse_moved_proxy = None # Signal proxy to limit mousMoved event rate self.__poi_selector_active = False # Flag indicating if the poi selector is active return def on_activate(self): """ Initializes the overall GUI, and establishes the connectors. This method executes the init methods for each of the GUIs. """ self._markers = dict() self._mw = PoiManagerMainWindow() # Configuring the dock widgets. # All our gui elements are dockable, so there should be no "central" widget. self._mw.centralwidget.hide() self._mw.setDockNestingEnabled(True) # Add validator to LineEdits self._mw.roi_name_LineEdit.setValidator(NameValidator()) self._mw.poi_name_LineEdit.setValidator(NameValidator()) self._mw.poi_nametag_LineEdit.setValidator(NameValidator(empty_allowed=True)) # Initialize plots self.__init_roi_scan_image() self.__init_roi_history_plot() # Initialize refocus timer self.update_refocus_timer(self.poimanagerlogic().module_state() == 'locked', self.poimanagerlogic().refocus_period, self.poimanagerlogic().refocus_period) # Initialize POIs self._update_pois(self.poimanagerlogic().poi_positions) # Initialize ROI name self._update_roi_name(self.poimanagerlogic().roi_name) # Initialize POI nametag self._update_poi_nametag(self.poimanagerlogic().poi_nametag) # Distance Measurement: # Introducing a SignalProxy will limit the rate of signals that get fired. self._mouse_moved_proxy = pg.SignalProxy(signal=self.roi_image.scene().sigMouseMoved, rateLimit=30, slot=self.mouse_moved_callback) # Connect signals self.__connect_internal_signals() self.__connect_update_signals_from_logic() self.__connect_control_signals_to_logic() self._mw.show() return def on_deactivate(self): """ De-initialisation performed during deactivation of the module. """ self.toggle_poi_selector(False) self.__disconnect_control_signals_to_logic() self.__disconnect_update_signals_from_logic() self.__disconnect_internal_signals() self._mw.close() def __init_roi_scan_image(self): # Get the color scheme my_colors = ColorScaleInferno() # Setting up display of ROI xy scan image self.roi_image = ScanImageItem(axisOrder='row-major', lut=my_colors.lut) self._mw.roi_map_ViewWidget.addItem(self.roi_image) self._mw.roi_map_ViewWidget.setLabel('bottom', 'X position', units='m') self._mw.roi_map_ViewWidget.setLabel('left', 'Y position', units='m') self._mw.roi_map_ViewWidget.setAspectLocked(lock=True, ratio=1.0) # Set up color bar self.roi_cb = ColorBar(my_colors.cmap_normed, 100, 0, 100000) self._mw.roi_cb_ViewWidget.addItem(self.roi_cb) self._mw.roi_cb_ViewWidget.hideAxis('bottom') self._mw.roi_cb_ViewWidget.setLabel('left', 'Fluorescence', units='c/s') self._mw.roi_cb_ViewWidget.setMouseEnabled(x=False, y=False) # Get scan image from logic and update initialize plot self._update_scan_image(self.poimanagerlogic().roi_scan_image, self.poimanagerlogic().roi_scan_image_extent) return def __init_roi_history_plot(self): # Setting up display of sample shift plot self.x_shift_plot = pg.PlotDataItem(x=[0], y=[0], pen=pg.mkPen(palette.c1, style=QtCore.Qt.DotLine), symbol='o', symbolPen=palette.c1, symbolBrush=palette.c1, symbolSize=5, name='x') self.y_shift_plot = pg.PlotDataItem(x=[0], y=[0], pen=pg.mkPen(palette.c2, style=QtCore.Qt.DotLine), symbol='s', symbolPen=palette.c2, symbolBrush=palette.c2, symbolSize=5, name='y') self.z_shift_plot = pg.PlotDataItem(x=[0], y=[0], pen=pg.mkPen(palette.c3, style=QtCore.Qt.DotLine), symbol='t', symbolPen=palette.c3, symbolBrush=palette.c3, symbolSize=5, name='z') self._mw.sample_shift_ViewWidget.addLegend() # Add the plot to the ViewWidget defined in the UI file self._mw.sample_shift_ViewWidget.addItem(self.x_shift_plot) self._mw.sample_shift_ViewWidget.addItem(self.y_shift_plot) self._mw.sample_shift_ViewWidget.addItem(self.z_shift_plot) # Label axes self._mw.sample_shift_ViewWidget.setLabel('bottom', 'Time', units='s') self._mw.sample_shift_ViewWidget.setLabel('left', 'Sample shift', units='m') self._update_roi_history(self.poimanagerlogic().roi_pos_history) return def __connect_update_signals_from_logic(self): self.poimanagerlogic().sigRefocusTimerUpdated.connect( self.update_refocus_timer, QtCore.Qt.QueuedConnection) self.poimanagerlogic().sigPoiUpdated.connect( self.update_poi, QtCore.Qt.QueuedConnection) self.poimanagerlogic().sigActivePoiUpdated.connect( self.update_active_poi, QtCore.Qt.QueuedConnection) self.poimanagerlogic().sigRoiUpdated.connect(self.update_roi, QtCore.Qt.QueuedConnection) self.poimanagerlogic().sigRefocusStateUpdated.connect( self.update_refocus_state, QtCore.Qt.QueuedConnection) return def __disconnect_update_signals_from_logic(self): self.poimanagerlogic().sigRefocusTimerUpdated.disconnect() self.poimanagerlogic().sigPoiUpdated.disconnect() self.poimanagerlogic().sigActivePoiUpdated.disconnect() self.poimanagerlogic().sigRoiUpdated.disconnect() self.poimanagerlogic().sigRefocusStateUpdated.disconnect() return def __connect_control_signals_to_logic(self): self._mw.new_poi_Action.triggered.connect( self.poimanagerlogic().add_poi, QtCore.Qt.QueuedConnection) self._mw.goto_poi_Action.triggered.connect( self.poimanagerlogic().go_to_poi, QtCore.Qt.QueuedConnection) self._mw.new_roi_Action.triggered.connect( self.poimanagerlogic().reset_roi, QtCore.Qt.QueuedConnection) self._mw.refind_poi_Action.triggered.connect( self.poimanagerlogic().optimise_poi_position, QtCore.Qt.QueuedConnection) self._mw.get_confocal_image_PushButton.clicked.connect( self.poimanagerlogic().set_scan_image, QtCore.Qt.QueuedConnection) self._mw.set_poi_PushButton.clicked.connect( self.poimanagerlogic().add_poi, QtCore.Qt.QueuedConnection) self._mw.delete_last_pos_Button.clicked.connect( self.poimanagerlogic().delete_history_entry, QtCore.Qt.QueuedConnection) self._mw.manual_update_poi_PushButton.clicked.connect( self.poimanagerlogic().move_roi_from_poi_position, QtCore.Qt.QueuedConnection) self._mw.move_poi_PushButton.clicked.connect( self.poimanagerlogic().set_poi_anchor_from_position, QtCore.Qt.QueuedConnection) self._mw.delete_poi_PushButton.clicked.connect( self.poimanagerlogic().delete_poi, QtCore.Qt.QueuedConnection) self._mw.active_poi_ComboBox.activated[str].connect( self.poimanagerlogic().set_active_poi, QtCore.Qt.QueuedConnection) self._mw.goto_poi_after_update_checkBox.stateChanged.connect( self.poimanagerlogic().set_move_scanner_after_optimise, QtCore.Qt.QueuedConnection) self._mw.track_poi_Action.triggered.connect( self.poimanagerlogic().toggle_periodic_refocus, QtCore.Qt.QueuedConnection) self.sigTrackPeriodChanged.connect( self.poimanagerlogic().set_refocus_period, QtCore.Qt.QueuedConnection) self.sigRoiNameChanged.connect( self.poimanagerlogic().rename_roi, QtCore.Qt.QueuedConnection) self.sigPoiNameChanged.connect( self.poimanagerlogic().rename_poi, QtCore.Qt.QueuedConnection) self.sigPoiNameTagChanged.connect( self.poimanagerlogic().set_poi_nametag, QtCore.Qt.QueuedConnection) self.sigAddPoiByClick.connect(self.poimanagerlogic().add_poi, QtCore.Qt.QueuedConnection) return def __disconnect_control_signals_to_logic(self): self._mw.new_poi_Action.triggered.disconnect() self._mw.goto_poi_Action.triggered.disconnect() self._mw.new_roi_Action.triggered.disconnect() self._mw.refind_poi_Action.triggered.disconnect() self._mw.get_confocal_image_PushButton.clicked.disconnect() self._mw.set_poi_PushButton.clicked.disconnect() self._mw.delete_last_pos_Button.clicked.disconnect() self._mw.manual_update_poi_PushButton.clicked.disconnect() self._mw.move_poi_PushButton.clicked.disconnect() self._mw.delete_poi_PushButton.clicked.disconnect() self._mw.active_poi_ComboBox.activated[str].disconnect() self._mw.goto_poi_after_update_checkBox.stateChanged.disconnect() self._mw.track_poi_Action.triggered.disconnect() self.sigTrackPeriodChanged.disconnect() self.sigRoiNameChanged.disconnect() self.sigPoiNameChanged.disconnect() self.sigPoiNameTagChanged.disconnect() self.sigAddPoiByClick.disconnect() for marker in self._markers.values(): marker.sigPoiSelected.disconnect() return def __connect_internal_signals(self): self._mw.track_period_SpinBox.editingFinished.connect(self.track_period_changed) self._mw.roi_name_LineEdit.editingFinished.connect(self.roi_name_changed) self._mw.poi_name_LineEdit.returnPressed.connect(self.poi_name_changed) self._mw.poi_nametag_LineEdit.editingFinished.connect(self.poi_nametag_changed) self._mw.save_roi_Action.triggered.connect(self.save_roi) self._mw.load_roi_Action.triggered.connect(self.load_roi) self._mw.blink_correction_view_Action.triggered.connect(self.toggle_blink_correction) self._mw.poi_selector_Action.toggled.connect(self.toggle_poi_selector) self._mw.roi_cb_centiles_RadioButton.toggled.connect(self.update_cb) self._mw.roi_cb_manual_RadioButton.toggled.connect(self.update_cb) self._mw.roi_cb_min_SpinBox.valueChanged.connect(self.update_cb_absolute) self._mw.roi_cb_max_SpinBox.valueChanged.connect(self.update_cb_absolute) self._mw.roi_cb_low_percentile_DoubleSpinBox.valueChanged.connect(self.update_cb_centiles) self._mw.roi_cb_high_percentile_DoubleSpinBox.valueChanged.connect(self.update_cb_centiles) return def __disconnect_internal_signals(self): self._mw.track_period_SpinBox.editingFinished.disconnect() self._mw.roi_name_LineEdit.editingFinished.disconnect() self._mw.poi_name_LineEdit.returnPressed.disconnect() self._mw.poi_nametag_LineEdit.editingFinished.disconnect() self._mw.save_roi_Action.triggered.disconnect() self._mw.load_roi_Action.triggered.disconnect() self._mw.blink_correction_view_Action.triggered.disconnect() self._mw.poi_selector_Action.toggled.disconnect() self._mw.roi_cb_centiles_RadioButton.toggled.disconnect() self._mw.roi_cb_manual_RadioButton.toggled.disconnect() self._mw.roi_cb_min_SpinBox.valueChanged.disconnect() self._mw.roi_cb_max_SpinBox.valueChanged.disconnect() self._mw.roi_cb_low_percentile_DoubleSpinBox.valueChanged.disconnect() self._mw.roi_cb_high_percentile_DoubleSpinBox.valueChanged.disconnect() return def show(self): """Make main window visible and put it above all other windows. """ QtWidgets.QMainWindow.show(self._mw) self._mw.activateWindow() self._mw.raise_() @QtCore.Slot(bool) def toggle_blink_correction(self, is_active): self.roi_image.activate_blink_correction(is_active) return @QtCore.Slot(object) def mouse_moved_callback(self, event): """ Handles any mouse movements inside the image. @param event: Event that signals the new mouse movement. This should be of type QPointF. Gets the mouse position, converts it to a position scaled to the image axis and than calculates and updated the position to the current POI. """ # converts the absolute mouse position to a position relative to the axis mouse_pos = self.roi_image.getViewBox().mapSceneToView(event[0]) # only calculate distance, if a POI is selected active_poi = self.poimanagerlogic().active_poi if active_poi: poi_pos = self.poimanagerlogic().get_poi_position(active_poi) dx = ScaledFloat(mouse_pos.x() - poi_pos[0]) dy = ScaledFloat(mouse_pos.y() - poi_pos[1]) d_total = ScaledFloat( np.sqrt((mouse_pos.x() - poi_pos[0])**2 + (mouse_pos.y() - poi_pos[1])**2)) self._mw.poi_distance_label.setText( '{0:.2r}m ({1:.2r}m, {2:.2r}m)'.format(d_total, dx, dy)) else: self._mw.poi_distance_label.setText('? (?, ?)') pass @QtCore.Slot(bool) def toggle_poi_selector(self, is_active): if is_active != self._mw.poi_selector_Action.isChecked(): self._mw.poi_selector_Action.blockSignals(True) self._mw.poi_selector_Action.setChecked(is_active) self._mw.poi_selector_Action.blockSignals(False) if is_active != self.__poi_selector_active: if is_active: self.roi_image.sigMouseClicked.connect(self.create_poi_from_click) self.roi_image.setCursor(QtCore.Qt.CrossCursor) else: self.roi_image.sigMouseClicked.disconnect() self.roi_image.setCursor(QtCore.Qt.ArrowCursor) self.__poi_selector_active = is_active return @QtCore.Slot(QtCore.Qt.MouseButton, QtCore.QPointF) def create_poi_from_click(self, button, pos): # Only create new POI if the mouse click event has not been accepted by some other means # In our case this is most likely the POI marker to select the active POI from. if button != QtCore.Qt.LeftButton: return # Z position from ROI origin, X and Y positions from click event new_pos = self.poimanagerlogic().roi_origin new_pos[0] = pos.x() new_pos[1] = pos.y() self.sigAddPoiByClick.emit(new_pos) return @QtCore.Slot(dict) def update_roi(self, roi_dict): if not isinstance(roi_dict, dict): self.log.error('ROI parameters to update must be given in a single dictionary.') return if 'name' in roi_dict: self._update_roi_name(name=roi_dict['name']) if 'poi_nametag' in roi_dict: self._update_poi_nametag(tag=roi_dict['poi_nametag']) if 'history' in roi_dict: self._update_roi_history(history=roi_dict['history']) if 'scan_image' in roi_dict and 'scan_image_extent' in roi_dict: self._update_scan_image(scan_image=roi_dict['scan_image'], image_extent=roi_dict['scan_image_extent']) if 'pois' in roi_dict: self._update_pois(poi_dict=roi_dict['pois']) return @QtCore.Slot(bool, float, float) def update_refocus_timer(self, is_active, period, time_until_refocus): if not self._mw.track_period_SpinBox.hasFocus(): self._mw.track_period_SpinBox.blockSignals(True) self._mw.track_period_SpinBox.setValue(period) self._mw.track_period_SpinBox.blockSignals(False) self._mw.track_poi_Action.blockSignals(True) self._mw.time_till_next_update_ProgressBar.blockSignals(True) self._mw.track_poi_Action.setChecked(is_active) self._mw.time_till_next_update_ProgressBar.setMaximum(period) self._mw.time_till_next_update_ProgressBar.setValue(time_until_refocus) self._mw.time_till_next_update_ProgressBar.blockSignals(False) self._mw.track_poi_Action.blockSignals(False) return @QtCore.Slot(bool) def update_refocus_state(self, is_active): self._mw.refind_poi_Action.setEnabled(not is_active) return @QtCore.Slot(str, str, np.ndarray) def update_poi(self, old_name, new_name, position): # Handle changed names and deleted/added POIs if old_name != new_name: self._mw.active_poi_ComboBox.blockSignals(True) # Remember current text text_active_poi = self._mw.active_poi_ComboBox.currentText() # sort POI names and repopulate ComboBoxes self._mw.active_poi_ComboBox.clear() poi_names = natural_sort(self.poimanagerlogic().poi_names) self._mw.active_poi_ComboBox.addItems(poi_names) if text_active_poi == old_name: self._mw.active_poi_ComboBox.setCurrentText(new_name) else: self._mw.active_poi_ComboBox.setCurrentText(text_active_poi) self._mw.active_poi_ComboBox.blockSignals(False) # Delete/add/update POI marker to image if not old_name: # POI has been added self._add_poi_marker(name=new_name, position=position) elif not new_name: # POI has been deleted self._remove_poi_marker(name=old_name) else: # POI has been renamed and/or changed position size = self.poimanagerlogic().optimise_xy_size * np.sqrt(2) self._markers[old_name].set_name(new_name) self._markers[new_name] = self._markers.pop(old_name) self._markers[new_name].setSize((size, size)) self._markers[new_name].set_position(position[:2]) active_poi = self._mw.active_poi_ComboBox.currentText() if active_poi: self._markers[active_poi].select() return @QtCore.Slot(str) def update_active_poi(self, name): # Deselect current marker for marker in self._markers.values(): if marker.selected: marker.deselect() break # Unselect POI if name is None or empty str self._mw.active_poi_ComboBox.blockSignals(True) if not name: self._mw.active_poi_ComboBox.setCurrentIndex(-1) else: self._mw.active_poi_ComboBox.setCurrentText(name) self._mw.active_poi_ComboBox.blockSignals(False) if name: active_poi_pos = self.poimanagerlogic().get_poi_position(name) else: active_poi_pos = np.zeros(3) self._mw.poi_coords_label.setText( '({0:.2r}m, {1:.2r}m, {2:.2r}m)'.format(ScaledFloat(active_poi_pos[0]), ScaledFloat(active_poi_pos[1]), ScaledFloat(active_poi_pos[2]))) if name in self._markers: self._markers[name].set_radius(self.poimanagerlogic().optimise_xy_size / np.sqrt(2)) self._markers[name].select() return @QtCore.Slot() def track_period_changed(self): self.sigTrackPeriodChanged.emit(self._mw.track_period_SpinBox.value()) return @QtCore.Slot() def roi_name_changed(self): """ Set the name of the current ROI.""" self.sigRoiNameChanged.emit(self._mw.roi_name_LineEdit.text()) return @QtCore.Slot() def poi_name_changed(self): """ Change the name of the active poi.""" new_name = self._mw.poi_name_LineEdit.text() if self._mw.active_poi_ComboBox.currentText() == new_name or not new_name: return self.sigPoiNameChanged.emit(new_name) # After POI name is changed, empty name field self._mw.poi_name_LineEdit.blockSignals(True) self._mw.poi_name_LineEdit.setText('') self._mw.poi_name_LineEdit.blockSignals(False) return @QtCore.Slot() def poi_nametag_changed(self): self.sigPoiNameTagChanged.emit(self._mw.poi_nametag_LineEdit.text()) return @QtCore.Slot() def save_roi(self): """ Save ROI to file.""" roi_name = self._mw.roi_name_LineEdit.text() self.poimanagerlogic().rename_roi(roi_name) self.poimanagerlogic().save_roi() return @QtCore.Slot() def load_roi(self): """ Load a saved ROI from file.""" this_file = QtWidgets.QFileDialog.getOpenFileName(self._mw, 'Open ROI', self.poimanagerlogic().data_directory, 'Data files (*.dat)')[0] if this_file: self.poimanagerlogic().load_roi(complete_path=this_file) return @QtCore.Slot() def update_cb_centiles(self): if not self._mw.roi_cb_centiles_RadioButton.isChecked(): self._mw.roi_cb_centiles_RadioButton.toggle() else: self.update_cb() return @QtCore.Slot() def update_cb_absolute(self): if not self._mw.roi_cb_manual_RadioButton.isChecked(): self._mw.roi_cb_manual_RadioButton.toggle() else: self.update_cb() return @QtCore.Slot() def update_cb(self): image = self.poimanagerlogic().roi_scan_image if image is None: return cb_range = self.get_cb_range(image) self.roi_image.setLevels(cb_range) self.roi_cb.refresh_colorbar(*cb_range) return def _update_scan_image(self, scan_image, image_extent): """ @param scan_image: @param image_extent: """ if scan_image is None or image_extent is None: self._mw.roi_map_ViewWidget.removeItem(self.roi_image) return elif self.roi_image not in self._mw.roi_map_ViewWidget.items(): self._mw.roi_map_ViewWidget.addItem(self.roi_image) self.roi_image.setImage(image=scan_image) (x_min, x_max), (y_min, y_max) = image_extent self.roi_image.getViewBox().enableAutoRange() self.roi_image.setRect(QtCore.QRectF(x_min, y_min, x_max - x_min, y_max - y_min)) self.update_cb() return def _update_roi_name(self, name): self._mw.roi_name_LineEdit.blockSignals(True) self._mw.roi_name_LineEdit.setText(name) self._mw.roi_name_LineEdit.blockSignals(False) return def _update_poi_nametag(self, tag): if tag is None: tag = '' self._mw.poi_nametag_LineEdit.blockSignals(True) self._mw.poi_nametag_LineEdit.setText(tag) self._mw.poi_nametag_LineEdit.blockSignals(False) return def _update_roi_history(self, history=None): if history is None: history = self.poimanagerlogic().roi_pos_history if history.shape[1] != 4: self.log.error('ROI history must be an array of type float[][4].') return max_time = np.max(history[:, 0]) if max_time < 300: self._mw.sample_shift_ViewWidget.setLabel('bottom', 'Time', units='s') time_arr = history[:, 0] elif max_time < 7200: self._mw.sample_shift_ViewWidget.setLabel('bottom', 'Time', units='min') time_arr = history[:, 0] / 60 elif max_time < 172800: self._mw.sample_shift_ViewWidget.setLabel('bottom', 'Time', units='h') time_arr = history[:, 0] / 3600 else: self._mw.sample_shift_ViewWidget.setLabel('bottom', 'Time', units='d') time_arr = history[:, 0] / 86400 self.x_shift_plot.setData(time_arr, history[:, 1]) self.y_shift_plot.setData(time_arr, history[:, 2]) self.z_shift_plot.setData(time_arr, history[:, 3]) return def _update_pois(self, poi_dict): """ Populate the dropdown box for selecting a poi. """ self._mw.active_poi_ComboBox.blockSignals(True) self._mw.active_poi_ComboBox.clear() poi_names = natural_sort(poi_dict) self._mw.active_poi_ComboBox.addItems(poi_names) # Get two list of POI names. One of those to delete and one of those to add old_poi_names = set(self._markers) new_poi_names = set(poi_names) names_to_delete = list(old_poi_names.difference(new_poi_names)) names_to_add = list(new_poi_names.difference(old_poi_names)) # Delete markers accordingly for name in names_to_delete: self._remove_poi_marker(name) # Update positions of all remaining markers size = self.poimanagerlogic().optimise_xy_size * np.sqrt(2) for name, marker in self._markers.items(): marker.setSize((size, size)) marker.set_position(poi_dict[name]) # Add new markers for name in names_to_add: self._add_poi_marker(name=name, position=poi_dict[name]) # If there is no active POI, set the combobox to nothing (-1) active_poi = self.poimanagerlogic().active_poi if active_poi in poi_names: self._mw.active_poi_ComboBox.setCurrentText(active_poi) self._markers[active_poi].select() active_poi_pos = poi_dict[active_poi] self._mw.poi_coords_label.setText( '({0:.2r}m, {1:.2r}m, {2:.2r}m)'.format(ScaledFloat(active_poi_pos[0]), ScaledFloat(active_poi_pos[1]), ScaledFloat(active_poi_pos[2]))) else: self._mw.active_poi_ComboBox.setCurrentIndex(-1) self._mw.active_poi_ComboBox.blockSignals(False) return def get_cb_range(self, image): """ Process UI input to determine color bar range""" # If "Centiles" is checked, adjust colour scaling automatically to centiles. # Otherwise, take user-defined values. if self._mw.roi_cb_centiles_RadioButton.isChecked(): low_centile = self._mw.roi_cb_low_percentile_DoubleSpinBox.value() high_centile = self._mw.roi_cb_high_percentile_DoubleSpinBox.value() cb_min = np.percentile(image, low_centile) cb_max = np.percentile(image, high_centile) else: cb_min = self._mw.roi_cb_min_SpinBox.value() cb_max = self._mw.roi_cb_max_SpinBox.value() return cb_min, cb_max def _add_poi_marker(self, name, position): """ Add a circular POI marker to the ROI scan image. """ if name: if name in self._markers: self.log.error('Unable to add POI marker to ROI image. POI marker already present.') return marker = PoiMarker(position=position[:2], view_widget=self._mw.roi_map_ViewWidget, poi_name=name, radius=self.poimanagerlogic().optimise_xy_size / np.sqrt(2), movable=False) # Add to the scan image widget marker.add_to_view_widget() marker.sigPoiSelected.connect( self.poimanagerlogic().set_active_poi, QtCore.Qt.QueuedConnection) self._markers[name] = marker return def _remove_poi_marker(self, name): """ Remove the POI marker for a POI that was deleted. """ if name in self._markers: self._markers[name].delete_from_view_widget() self._markers[name].sigPoiSelected.disconnect() del self._markers[name] return
class VoltScanGui(GUIBase): """ This is the GUI Class for ODMR """ _modclass = 'VoltScanGui' _modtype = 'gui' ## declare connectors _in = {'voltagescannerlogic1': 'VoltageScannerLogic', } def __init__(self, config, **kwargs): super().__init__(config=config, **kwargs) self.log.info('The following configuration was found.') # checking for the right configuration for key in config.keys(): self.log.info('{0}: {1}'.format(key,config[key])) def on_deactivate(self, e): """ Reverse steps of activation @param e: error code @return int: error code (0:OK, -1:error) """ self._mw.close() return 0 def on_activate(self, e=None): """ Definition, configuration and initialisation of the ODMR GUI. @param class e: event class from Fysom This init connects all the graphic modules, which were created in the *.ui file and configures the event handling between the modules. """ self._voltscan_logic = self.get_in_connector('odmrlogic1') print("ODMR logic is", self._odmr_logic) # Use the inherited class 'Ui_VoltagescannerGuiUI' to create now the # GUI element: self._mw = VoltScanMainWindow() # Get the image from the logic self.odmr_matrix_image = pg.ImageItem(self._odmr_logic.ODMR_plot_xy.transpose()) self.odmr_matrix_image.setRect(QtCore.QRectF(self._odmr_logic.mw_start,0,self._odmr_logic.mw_stop-self._odmr_logic.mw_start,self._odmr_logic.number_of_lines)) self.odmr_image = pg.PlotDataItem(self._odmr_logic.ODMR_plot_x,self._odmr_logic.ODMR_plot_y) self.odmr_fit_image = pg.PlotDataItem(self._odmr_logic.ODMR_fit_x,self._odmr_logic.ODMR_fit_y, pen=QtGui.QPen(QtGui.QColor(255,255,255,255))) # Add the display item to the xy and xz VieWidget, which was defined in # the UI file. self._mw.voltscan_ViewWidget.addItem(self.odmr_image) self._mw.voltscan_ViewWidget.addItem(self.odmr_fit_image) self._mw.voltscan_matrix_ViewWidget.addItem(self.odmr_matrix_image) self._mw.vonsoltscan_ViewWidget.showGrid(x=True, y=True, alpha=0.8) # Get the colorscales at set LUT my_colors = ColorScaleInferno() self.odmr_matrix_image.setLookupTable(my_colors.lut) # Set the state button as ready button as default setting. # self._mw.idle_StateWidget.click() # Configuration of the comboWidget self._mw.mode_ComboWidget.addItem('Off') self._mw.mode_ComboWidget.addItem('CW') self._mw.fit_methods_ComboWidget.addItem('No Fit') self._mw.fit_methods_ComboWidget.addItem('Lorentzian') self._mw.fit_methods_ComboWidget.addItem('Double Lorentzian') self._mw.fit_methods_ComboWidget.addItem('Double Lorentzian with fixed splitting') self._mw.fit_methods_ComboWidget.addItem('N14') self._mw.fit_methods_ComboWidget.addItem('N15') ####################################################################### ## Configuration of the Colorbar ## ####################################################################### self.odmr_cb = ColorBar(my_colors.cmap_normed, 100, 0, 100000) #adding colorbar to ViewWidget self._mw.odmr_cb_ViewWidget.addItem(self.odmr_cb) self._mw.odmr_cb_ViewWidget.hideAxis('bottom') self._mw.odmr_cb_ViewWidget.hideAxis('left') self._mw.odmr_cb_ViewWidget.setLabel('right', 'Fluorescence', units='c/s') # Connect the buttons and inputs for the odmr colorbar self._mw.odmr_cb_manual_RadioButton.clicked.connect(self.refresh_matrix) self._mw.odmr_cb_centiles_RadioButton.clicked.connect(self.refresh_matrix) ####################################################################### ## Configuration of the InputWidgets ## ####################################################################### # Add Validators to InputWidgets validator = QtGui.QDoubleValidator() validator2 = QtGui.QIntValidator() self._mw.frequency_InputWidget.setValidator(validator) self._mw.start_freq_InputWidget.setValidator(validator) self._mw.step_freq_InputWidget.setValidator(validator) self._mw.stop_freq_InputWidget.setValidator(validator) self._mw.power_InputWidget.setValidator(validator) self._mw.runtime_InputWidget.setValidator(validator2) self._sd.matrix_lines_InputWidget.setValidator(validator) self._sd.clock_frequency_InputWidget.setValidator(validator2) # Take the default values from logic: self._mw.frequency_InputWidget.setText(str(self._odmr_logic.mw_frequency)) self._mw.start_freq_InputWidget.setText(str(self._odmr_logic.mw_start)) self._mw.step_freq_InputWidget.setText(str(self._odmr_logic.mw_step)) self._mw.stop_freq_InputWidget.setText(str(self._odmr_logic.mw_stop)) self._mw.power_InputWidget.setText(str(self._odmr_logic.mw_power)) self._mw.runtime_InputWidget.setText(str(self._odmr_logic.run_time)) self._mw.elapsed_time_DisplayWidget.display(int(self._odmr_logic.ElapsedTime)) self._sd.matrix_lines_InputWidget.setText(str(self._odmr_logic.number_of_lines)) self._sd.clock_frequency_InputWidget.setText(str(self._odmr_logic._clock_frequency)) # Update the inputed/displayed numbers if return key is hit: self._mw.frequency_InputWidget.returnPressed.connect(self.change_frequency) self._mw.start_freq_InputWidget.returnPressed.connect(self.change_start_freq) self._mw.step_freq_InputWidget.returnPressed.connect(self.change_step_freq) self._mw.stop_freq_InputWidget.returnPressed.connect(self.change_stop_freq) self._mw.power_InputWidget.returnPressed.connect(self.change_power) self._mw.runtime_InputWidget.returnPressed.connect(self.change_runtime) # Update the inputed/displayed numbers if the cursor has left the field: self._mw.frequency_InputWidget.editingFinished.connect(self.change_frequency) self._mw.start_freq_InputWidget.editingFinished.connect(self.change_start_freq) self._mw.step_freq_InputWidget.editingFinished.connect(self.change_step_freq) self._mw.stop_freq_InputWidget.editingFinished.connect(self.change_stop_freq) self._mw.power_InputWidget.editingFinished.connect(self.change_power) self._mw.runtime_InputWidget.editingFinished.connect(self.change_runtime) # self._mw.odmr_cb_max_InputWidget.valueChanged.connect(self.refresh_matrix) self._mw.odmr_cb_min_InputWidget.valueChanged.connect(self.refresh_matrix) self._mw.odmr_cb_high_centile_InputWidget.valueChanged.connect(self.refresh_matrix) self._mw.odmr_cb_low_centile_InputWidget.valueChanged.connect(self.refresh_matrix) ####################################################################### ## Connect signals ## ####################################################################### # Connect the RadioButtons and connect to the events if they are clicked: # self._mw.idle_StateWidget.toggled.connect(self.idle_clicked) # self._mw.run_StateWidget.toggled.connect(self.run_clicked) self._mw.action_run_stop.toggled.connect(self.run_stop) self._mw.action_Save.triggered.connect(self._odmr_logic.save_ODMR_Data) self._odmr_logic.sigOdmrPlotUpdated.connect(self.refresh_plot) self._odmr_logic.sigOdmrPlotUpdated.connect(self.refresh_matrix) self._odmr_logic.sigOdmrElapsedTimeChanged.connect(self.refresh_elapsedtime) # connect settings signals self._mw.action_Settings.triggered.connect(self.menue_settings) self._sd.accepted.connect(self.update_settings) self._sd.rejected.connect(self.reject_settings) self._sd.buttonBox.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect(self.update_settings) self.reject_settings() # Connect stop odmr # self._odmr_logic.sigOdmrFinished.connect(self._mw.idle_StateWidget.click) self._odmr_logic.sigOdmrFinished.connect(self.odmr_stopped) # Combo Widget self._mw.mode_ComboWidget.activated[str].connect(self.mw_stop) self._mw.fit_methods_ComboWidget.activated[str].connect(self.update_fit_variable) # Push Buttons self._mw.do_fit_PushButton.clicked.connect(self.update_fit) # Show the Main ODMR GUI: self._mw.show() def show(self): """Make window visible and put it above all other windows. """ QtWidgets.QMainWindow.show(self._mw) self._mw.activateWindow() self._mw.raise_() # def idle_clicked(self): # """ Stopp the scan if the state has switched to idle. """ # self._odmr_logic.stop_odmr_scan() # self._sd.matrix_lines_InputWidget.setReadOnly(False) # # self._odmr_logic.kill_odmr() # # # def run_clicked(self, enabled): # """ Manages what happens if odmr scan is started. # # @param bool enabled: start scan if that is possible # """ # # #Firstly stop any scan that might be in progress # self._odmr_logic.stop_odmr_scan() # # self._odmr_logic.kill_odmr() # #Then if enabled. start a new odmr scan. # if enabled: # self._odmr_logic.start_odmr_scan() # self._sd.matrix_lines_InputWidget.setReadOnly(True) def run_stop(self, is_checked): """ Manages what happens if odmr scan is started/stopped """ if is_checked: self._odmr_logic.stop_odmr_scan() self._odmr_logic.start_odmr_scan() self._mw.odmr_ViewWidget.removeItem(self.odmr_fit_image) self._sd.matrix_lines_InputWidget.setReadOnly(True) else: self._odmr_logic.stop_odmr_scan() self._sd.matrix_lines_InputWidget.setReadOnly(False) def odmr_stopped(self): """ Switch the run/stop button to stop after receiving an odmr_stoped signal """ self._mw.action_run_stop.setChecked(False) def menue_settings(self): """ Open the settings menue """ self._sd.exec_() def refresh_plot(self): """ Refresh the xy-plot image """ self.odmr_image.setData(self._odmr_logic.ODMR_plot_x,self._odmr_logic.ODMR_plot_y) if not self._mw.fit_methods_ComboWidget.currentText() == 'No Fit': self.odmr_fit_image.setData(self._odmr_logic.ODMR_fit_x,self._odmr_logic.ODMR_fit_y,pen=QtGui.QPen(QtGui.QColor(255,0,255,255))) else: if self.odmr_fit_image in self._mw.odmr_ViewWidget.listDataItems(): self._mw.odmr_ViewWidget.removeItem(self.odmr_fit_image) def refresh_matrix(self): """ Refresh the xy-matrix image """ # self.odmr_matrix_image.setImage(self._odmr_logic.ODMR_plot_xy.transpose()) # self.odmr_matrix_image.setRect(QtCore.QRectF(self._odmr_logic.mw_start,0,self._odmr_logic.mw_stop-self._odmr_logic.mw_start,self._odmr_logic.number_of_lines)) # self.refresh_odmr_colorbar() odmr_image_data = self._odmr_logic.ODMR_plot_xy.transpose() # If "Centiles" is checked, adjust colour scaling automatically to centiles. # Otherwise, take user-defined values. if self._mw.odmr_cb_centiles_RadioButton.isChecked(): low_centile = self._mw.odmr_cb_low_centile_InputWidget.value() high_centile = self._mw.odmr_cb_high_centile_InputWidget.value() cb_min = np.percentile( odmr_image_data, low_centile ) cb_max = np.percentile( odmr_image_data, high_centile ) else: cb_min = self._mw.odmr_cb_min_InputWidget.value() cb_max = self._mw.odmr_cb_max_InputWidget.value() # Now update image with new color scale, and update colorbar self.odmr_matrix_image.setImage(image=odmr_image_data, levels=(cb_min, cb_max) ) self.odmr_matrix_image.setRect(QtCore.QRectF(self._odmr_logic.mw_start,0,self._odmr_logic.mw_stop-self._odmr_logic.mw_start,self._odmr_logic.number_of_lines)) self.refresh_odmr_colorbar() def refresh_odmr_colorbar(self): """ Update the colorbar to a new scaling.""" # If "Centiles" is checked, adjust colour scaling automatically to centiles. # Otherwise, take user-defined values. if self._mw.odmr_cb_centiles_RadioButton.isChecked(): low_centile = self._mw.odmr_cb_low_centile_InputWidget.value() high_centile = self._mw.odmr_cb_high_centile_InputWidget.value() cb_min = np.percentile( self.odmr_matrix_image.image, low_centile ) cb_max = np.percentile( self.odmr_matrix_image.image, high_centile ) else: cb_min = self._mw.odmr_cb_min_InputWidget.value() cb_max = self._mw.odmr_cb_max_InputWidget.value() self.odmr_cb.refresh_colorbar(cb_min,cb_max) self._mw.odmr_cb_ViewWidget.update() def refresh_elapsedtime(self): self._mw.elapsed_time_DisplayWidget.display(int(self._odmr_logic.ElapsedTime)) def update_settings(self): """ Write the new settings from the gui to the file. """ self._odmr_logic.number_of_lines = int(self._sd.matrix_lines_InputWidget.text()) self._odmr_logic.set_clock_frequency(int(self._sd.clock_frequency_InputWidget.text())) self._odmr_logic.safeRawData = self._sd.save_raw_data_box.isChecked() def update_fit_variable(self, txt): self._odmr_logic.current_fit_function = txt def update_fit(self): self._odmr_logic.do_fit(fit_function = self._odmr_logic.current_fit_function) self.refresh_plot() # check which Fit method is used and remove or add again the # odmr_fit_image, check also whether a odmr_fit_image already exists. if self._mw.fit_methods_ComboWidget.currentText() == 'No Fit': if self.odmr_fit_image in self._mw.odmr_ViewWidget.listDataItems(): self._mw.odmr_ViewWidget.removeItem(self.odmr_fit_image) else: if self.odmr_fit_image not in self._mw.odmr_ViewWidget.listDataItems(): self._mw.odmr_ViewWidget.addItem(self.odmr_fit_image) self._mw.odmr_ViewWidget.getViewBox().updateAutoRange() self._mw.odmr_fit_results_DisplayWidget.clear() self._mw.odmr_fit_results_DisplayWidget.setPlainText(str(self._odmr_logic.fit_result)) def reject_settings(self): """ Keep the old settings and restores the old settings in the gui. """ self._sd.matrix_lines_InputWidget.setText(str(self._odmr_logic.number_of_lines)) self._sd.clock_frequency_InputWidget.setText(str(self._odmr_logic._clock_frequency)) self._sd.save_raw_data_box.setChecked(self._odmr_logic.safeRawData) def mw_stop(self, txt): if txt == 'Off': self._odmr_logic.MW_off() if txt == 'CW': self.change_frequency() self.change_power() self._odmr_logic.MW_on() ########################################################################### ## Change Methods ## ########################################################################### def change_frequency(self): self._odmr_logic.set_frequency(frequency = float(self._mw.frequency_InputWidget.text())) def change_start_freq(self): self._odmr_logic.mw_start = float(self._mw.start_freq_InputWidget.text()) def change_step_freq(self): self._odmr_logic.mw_step = float(self._mw.step_freq_InputWidget.text()) def change_stop_freq(self): self._odmr_logic.mw_stop = float(self._mw.stop_freq_InputWidget.text()) def change_power(self): self._odmr_logic.mw_power = float(self._mw.power_InputWidget.text()) self._odmr_logic.set_power(power = self._odmr_logic.mw_power) def change_runtime(self): self._odmr_logic.run_time = float(self._mw.runtime_InputWidget.text())
class ColorbarWidget(QtWidgets.QWidget): """ Create the widget, based on the corresponding *.ui file.""" def __init__(self, image_widget, unit='c/s'): # Get the path to the *.ui file this_dir = os.path.dirname(__file__) ui_file = os.path.join(this_dir, 'ui_colorbar.ui') # Load it super(ColorbarWidget, self).__init__() uic.loadUi(ui_file, self) self._cb_min = 0 self._cb_max = 100 self.unit = unit self.init_spin_box() self.init_colorbar() self.set_image(image_widget) self.percentile.clicked.emit() self.percentile.setChecked(True) def init_spin_box(self): """ Initialize all the spinboxes """ self._min_percentile = ScienDSpinBox() self._min_manual = ScienDSpinBox() self._max_percentile = ScienDSpinBox() self._max_manual = ScienDSpinBox() self._min_percentile.setSuffix('%') self._min_percentile.setMinimum(0) self._min_percentile.setMaximum(100) self._min_percentile.setValue(0) self._min_manual.setSuffix(self.unit) self._max_percentile.setSuffix('%') self._max_percentile.setMinimum(0) self._max_percentile.setMaximum(100) self._max_percentile.setValue(100) self._max_manual.setSuffix(self.unit) self.min.addWidget(self._min_manual) self.min.addWidget(self._min_percentile) self.max.addWidget(self._max_percentile) self.max.addWidget(self._max_manual) self._min_percentile.valueChanged.connect(self.shortcut_to_cb_centiles) self._min_manual.valueChanged.connect(self.shortcut_to_cb_manual) self._max_percentile.valueChanged.connect(self.shortcut_to_cb_centiles) self._max_manual.valueChanged.connect(self.shortcut_to_cb_manual) self.manual.clicked.connect(self.update_cb_range) self.percentile.clicked.connect(self.update_cb_range) def init_colorbar(self): """ Create the colorbar """ self.my_colors = ColorScaleInferno() self._color_map = ColorScaleMagma() self._cb = ColorBar(self.my_colors.cmap_normed, width=100, cb_min=self._cb_min, cb_max=self._cb_max) self.colorbar.addItem(self._cb) self.colorbar.hideAxis('bottom') self.colorbar.setLabel('left', 'Intensity', units=self.unit) self.colorbar.setMouseEnabled(x=False, y=False) def set_image(self, image_widget): """ Set the image widget associated to the colorbar """ self._image = image_widget self._min_manual.setValue(np.min(self._image.image)) self._min_percentile.setValue(0) self._max_manual.setValue(np.max(self._image.image)) self._max_percentile.setValue(100) self.refresh_colorbar() def get_cb_range(self): """ Determines the cb_min and cb_max values for the image """ # If "Manual" is checked, or the image data is empty (all zeros), then take manual cb range. if self.manual.isChecked() or np.count_nonzero(self._image.image) < 1: cb_min = self._min_manual.value() cb_max = self._max_manual.value() # Otherwise, calculate cb range from percentiles. else: # Exclude any zeros (which are typically due to unfinished scan) image_nonzero = self._image.image[np.nonzero(self._image.image)] # Read centile range low_centile = self._min_percentile.value() high_centile = self._max_percentile.value() cb_min = np.percentile(image_nonzero, low_centile) cb_max = np.percentile(image_nonzero, high_centile) cb_range = [cb_min, cb_max] return cb_range def refresh_colorbar(self): """ Adjust the colorbar. """ cb_range = self.get_cb_range() self._cb.refresh_colorbar(cb_range[0], cb_range[1]) def refresh_image(self): """ Update the image colors range.""" image_data = self._image.image cb_range = self.get_cb_range() self._image.setImage(image=image_data, levels=(cb_range[0], cb_range[1])) self.refresh_colorbar() def update_cb_range(self): """ Redraw colour bar and image.""" self.refresh_colorbar() self.refresh_image() def shortcut_to_cb_manual(self): """ Someone edited the absolute counts range for the colour bar, better update.""" self.manual.setChecked(True) self.update_cb_range() def shortcut_to_cb_centiles(self): """Someone edited the centiles range for the colour bar, better update.""" self.percentile.setChecked(True) self.update_cb_range()