class QDPlotterGui(GUIBase): """ GUI for displaying up to 3 custom plots. The plots are held in tabified DockWidgets and can either be manipulated in the logic or by corresponding parameter DockWidgets. Example config for copy-paste: qdplotter: module.Class: 'qdplotter.qdplotter_gui.QDPlotterGui' pen_color_list: [[100, 100, 100], 'c', 'm', 'g'] connect: qdplot_logic: 'qdplotlogic' """ sigPlotParametersChanged = QtCore.Signal(int, dict) sigAutoRangeClicked = QtCore.Signal(int, bool, bool) sigDoFit = QtCore.Signal(str, int) sigRemovePlotClicked = QtCore.Signal(int) # declare connectors qdplot_logic = Connector(interface='QDPlotLogic') # declare config options _pen_color_list = ConfigOption(name='pen_color_list', default=['b', 'y', 'm', 'g']) # declare status variables widget_alignment = StatusVar(name='widget_alignment', default='tabbed') _allowed_colors = {'b', 'g', 'r', 'c', 'm', 'y', 'k', 'w'} def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._plot_logic = None self._mw = None self._fsd = None self._plot_dockwidgets = list() self._pen_colors = list() self._plot_curves = list() self._fit_curves = list() self._pg_signal_proxys = list() def on_activate(self): """ Definition and initialisation of the GUI. """ self._plot_logic = self.qdplot_logic() if not isinstance(self._pen_color_list, (list, tuple)) or len(self._pen_color_list) < 1: self.log.warning( 'The ConfigOption pen_color_list needs to be a list of strings but was "{0}".' ' Will use the following pen colors as default: {1}.' ''.format(self._pen_color_list, ['b', 'y', 'm', 'g'])) self._pen_color_list = ['b', 'y', 'm', 'g'] else: self._pen_color_list = list(self._pen_color_list) for index, color in enumerate(self._pen_color_list): if (isinstance(color, (list, tuple)) and len(color) == 3) or \ (isinstance(color, str) and color in self._allowed_colors): pass else: self.log.warning( 'The color was "{0}" but needs to be from this list: {1} ' 'or a 3 element tuple with values from 0 to 255 for RGB.' ' Setting color to "b".'.format(color, self._allowed_colors)) self._pen_color_list[index] = 'b' # Use the inherited class 'QDPlotMainWindow' to create the GUI window self._mw = QDPlotMainWindow() # Fit settings dialogs self._fsd = FitSettingsDialog(self._plot_logic.fit_container) self._fsd.applySettings() self._mw.fit_settings_Action.triggered.connect(self._fsd.show) # Connect the main window restore view actions self._mw.restore_tabbed_view_Action.triggered.connect( self.restore_tabbed_view) self._mw.restore_side_by_side_view_Action.triggered.connect( self.restore_side_by_side_view) self._mw.restore_arc_view_Action.triggered.connect( self.restore_arc_view) self._mw.save_all_Action.triggered.connect(self.save_all_clicked) # Initialize dock widgets self._plot_dockwidgets = list() self._pen_colors = list() self._plot_curves = list() self._fit_curves = list() self._pg_signal_proxys = list() self.update_number_of_plots(self._plot_logic.number_of_plots) # Update all plot parameters and data from logic for index, _ in enumerate(self._plot_dockwidgets): self.update_data(index) self.update_fit_data(index) self.update_plot_parameters(index) self.restore_view() # Connect signal to logic self.sigPlotParametersChanged.connect( self._plot_logic.update_plot_parameters, QtCore.Qt.QueuedConnection) self.sigAutoRangeClicked.connect(self._plot_logic.update_auto_range, QtCore.Qt.QueuedConnection) self.sigDoFit.connect(self._plot_logic.do_fit, QtCore.Qt.QueuedConnection) self.sigRemovePlotClicked.connect(self._plot_logic.remove_plot, QtCore.Qt.QueuedConnection) self._mw.new_plot_Action.triggered.connect(self._plot_logic.add_plot, QtCore.Qt.QueuedConnection) # Connect signals from logic self._plot_logic.sigPlotDataUpdated.connect(self.update_data, QtCore.Qt.QueuedConnection) self._plot_logic.sigPlotParamsUpdated.connect( self.update_plot_parameters, QtCore.Qt.QueuedConnection) self._plot_logic.sigPlotNumberChanged.connect( self.update_number_of_plots, QtCore.Qt.QueuedConnection) self._plot_logic.sigFitUpdated.connect(self.update_fit_data, QtCore.Qt.QueuedConnection) self.show() def show(self): """ Make window visible and put it above all other windows. """ self._mw.show() self._mw.activateWindow() self._mw.raise_() def on_deactivate(self): """ Deactivate the module """ # disconnect fit self._mw.fit_settings_Action.triggered.disconnect() self._mw.restore_tabbed_view_Action.triggered.disconnect() self._mw.restore_side_by_side_view_Action.triggered.disconnect() self._mw.restore_arc_view_Action.triggered.disconnect() self._mw.save_all_Action.triggered.disconnect() # Disconnect signal to logic self.sigPlotParametersChanged.disconnect() self.sigAutoRangeClicked.disconnect() self.sigDoFit.disconnect() self.sigRemovePlotClicked.disconnect() self._mw.new_plot_Action.triggered.disconnect() # Disconnect signals from logic self._plot_logic.sigPlotDataUpdated.disconnect(self.update_data) self._plot_logic.sigPlotParamsUpdated.disconnect( self.update_plot_parameters) self._plot_logic.sigPlotNumberChanged.disconnect( self.update_number_of_plots) self._plot_logic.sigFitUpdated.disconnect(self.update_fit_data) # disconnect GUI elements self.update_number_of_plots(0) self._fsd.sigFitsUpdated.disconnect() self._mw.close() @QtCore.Slot(int) def update_number_of_plots(self, count): """ Adjust number of QDockWidgets to current number of plots. Does NO initialization of the contents. @param int count: Number of plots to display. """ # Remove dock widgets if plot count decreased while count < len(self._plot_dockwidgets): index = len(self._plot_dockwidgets) - 1 self._disconnect_plot_signals(index) self._plot_dockwidgets[-1].setParent(None) del self._plot_curves[-1] del self._fit_curves[-1] del self._pen_colors[-1] del self._plot_dockwidgets[-1] del self._pg_signal_proxys[-1] # Add dock widgets if plot count increased while count > len(self._plot_dockwidgets): index = len(self._plot_dockwidgets) dockwidget = PlotDockWidget('Plot {0:d}'.format(index + 1), self._mw) dockwidget.widget().fit_comboBox.setFitFunctions( self._fsd.currentFits) dockwidget.widget().show_fit_checkBox.setChecked(False) dockwidget.widget().show_controls_checkBox.setChecked(False) dockwidget.widget().fit_groupBox.setVisible(False) dockwidget.widget().controls_groupBox.setVisible(False) self._plot_dockwidgets.append(dockwidget) self._pen_colors.append(cycle(self._pen_color_list)) self._plot_curves.append(list()) self._fit_curves.append(list()) self._pg_signal_proxys.append([None, None]) self._connect_plot_signals(index) self.restore_view() def _connect_plot_signals(self, index): dockwidget = self._plot_dockwidgets[index].widget() self._fsd.sigFitsUpdated.connect( dockwidget.fit_comboBox.setFitFunctions) dockwidget.fit_pushButton.clicked.connect( functools.partial(self.fit_clicked, index)) x_lim_callback = functools.partial(self.x_limits_changed, index) dockwidget.x_lower_limit_DoubleSpinBox.valueChanged.connect( x_lim_callback) dockwidget.x_upper_limit_DoubleSpinBox.valueChanged.connect( x_lim_callback) y_lim_callback = functools.partial(self.y_limits_changed, index) dockwidget.y_lower_limit_DoubleSpinBox.valueChanged.connect( y_lim_callback) dockwidget.y_upper_limit_DoubleSpinBox.valueChanged.connect( y_lim_callback) dockwidget.x_label_lineEdit.editingFinished.connect( functools.partial(self.x_label_changed, index)) dockwidget.x_unit_lineEdit.editingFinished.connect( functools.partial(self.x_unit_changed, index)) dockwidget.y_label_lineEdit.editingFinished.connect( functools.partial(self.y_label_changed, index)) dockwidget.y_unit_lineEdit.editingFinished.connect( functools.partial(self.y_unit_changed, index)) dockwidget.x_auto_PushButton.clicked.connect( functools.partial(self.x_auto_range_clicked, index)) dockwidget.y_auto_PushButton.clicked.connect( functools.partial(self.y_auto_range_clicked, index)) dockwidget.save_pushButton.clicked.connect( functools.partial(self.save_clicked, index)) dockwidget.remove_pushButton.clicked.connect( functools.partial(self.remove_clicked, index)) self._pg_signal_proxys[index][0] = SignalProxy( dockwidget.plot_PlotWidget.sigXRangeChanged, delay=0.2, slot=lambda args: self._pyqtgraph_x_limits_changed(index, args[1])) self._pg_signal_proxys[index][1] = SignalProxy( dockwidget.plot_PlotWidget.sigYRangeChanged, delay=0.2, slot=lambda args: self._pyqtgraph_y_limits_changed(index, args[1])) def _disconnect_plot_signals(self, index): dockwidget = self._plot_dockwidgets[index].widget() self._fsd.sigFitsUpdated.disconnect( dockwidget.fit_comboBox.setFitFunctions) dockwidget.fit_pushButton.clicked.disconnect() dockwidget.x_lower_limit_DoubleSpinBox.valueChanged.disconnect() dockwidget.x_upper_limit_DoubleSpinBox.valueChanged.disconnect() dockwidget.y_lower_limit_DoubleSpinBox.valueChanged.disconnect() dockwidget.y_upper_limit_DoubleSpinBox.valueChanged.disconnect() dockwidget.x_label_lineEdit.editingFinished.disconnect() dockwidget.x_unit_lineEdit.editingFinished.disconnect() dockwidget.y_label_lineEdit.editingFinished.disconnect() dockwidget.y_unit_lineEdit.editingFinished.disconnect() dockwidget.x_auto_PushButton.clicked.disconnect() dockwidget.y_auto_PushButton.clicked.disconnect() dockwidget.save_pushButton.clicked.disconnect() dockwidget.remove_pushButton.clicked.disconnect() for sig_proxy in self._pg_signal_proxys[index]: sig_proxy.sigDelayed.disconnect() sig_proxy.disconnect() @property def pen_color_list(self): return self._pen_color_list.copy() @pen_color_list.setter def pen_color_list(self, value): if not isinstance(value, (list, tuple)) or len(value) < 1: self.log.warning( 'The parameter pen_color_list needs to be a list of strings but was "{0}".' ' Will use the following old pen colors: {1}.' ''.format(value, self._pen_color_list)) return for index, color in enumerate(self._pen_color_list): if (isinstance(color, (list, tuple)) and len(color) == 3) or \ (isinstance(color, str) and color in self._allowed_colors): pass else: self.log.warning( 'The color was "{0}" but needs to be from this list: {1} ' 'or a 3 element tuple with values from 0 to 255 for RGB.' ''.format(color, self._allowed_colors)) return else: self._pen_color_list = list(value) def restore_side_by_side_view(self): """ Restore the arrangement of DockWidgets to the default """ self.restore_view(alignment='side_by_side') def restore_arc_view(self): """ Restore the arrangement of DockWidgets to the default """ self.restore_view(alignment='arc') def restore_tabbed_view(self): """ Restore the arrangement of DockWidgets to the default """ self.restore_view(alignment='tabbed') @QtCore.Slot() def restore_view(self, alignment=None): """ Restore the arrangement of DockWidgets to the default """ if alignment is None: alignment = self.widget_alignment if alignment not in ('side_by_side', 'arc', 'tabbed'): alignment = 'tabbed' self.widget_alignment = alignment self._mw.setDockNestingEnabled(True) self._mw.centralwidget.setVisible(False) for i, dockwidget in enumerate(self._plot_dockwidgets): dockwidget.show() dockwidget.setFloating(False) dockwidget.widget().show_fit_checkBox.setChecked(False) dockwidget.widget().show_controls_checkBox.setChecked(False) if alignment == 'tabbed': self._mw.addDockWidget(QtCore.Qt.TopDockWidgetArea, dockwidget) if i > 0: self._mw.tabifyDockWidget(self._plot_dockwidgets[0], dockwidget) elif alignment == 'arc': mod = i % 3 if mod == 0: self._mw.addDockWidget(QtCore.Qt.TopDockWidgetArea, dockwidget) if i > 2: self._mw.tabifyDockWidget(self._plot_dockwidgets[0], dockwidget) elif mod == 1: self._mw.addDockWidget(QtCore.Qt.BottomDockWidgetArea, dockwidget) if i > 2: self._mw.tabifyDockWidget(self._plot_dockwidgets[1], dockwidget) elif mod == 2: self._mw.addDockWidget(QtCore.Qt.BottomDockWidgetArea, dockwidget) if i > 2: self._mw.tabifyDockWidget(self._plot_dockwidgets[2], dockwidget) elif alignment == 'side_by_side': self._mw.addDockWidget(QtCore.Qt.TopDockWidgetArea, dockwidget) if alignment == 'arc': if len(self._plot_dockwidgets) > 2: self._mw.resizeDocks( [self._plot_dockwidgets[1], self._plot_dockwidgets[2]], [1, 1], QtCore.Qt.Horizontal) elif alignment == 'side_by_side': self._mw.resizeDocks(self._plot_dockwidgets, [1] * len(self._plot_dockwidgets), QtCore.Qt.Horizontal) @QtCore.Slot(int, list, list, bool) def update_data(self, plot_index, x_data=None, y_data=None, clear_old=None): """ Function creates empty plots, grabs the data and sends it to them. """ if not (0 <= plot_index < len(self._plot_dockwidgets)): self.log.warning( 'Tried to update plot with invalid index {0:d}'.format( plot_index)) return if x_data is None: x_data = self._plot_logic.get_x_data(plot_index) if y_data is None: y_data = self._plot_logic.get_y_data(plot_index) if clear_old is None: clear_old = self._plot_logic.clear_old_data(plot_index) dockwidget = self._plot_dockwidgets[plot_index].widget() if clear_old: dockwidget.plot_PlotWidget.clear() self._pen_colors[plot_index] = cycle(self._pen_color_list) self._plot_curves[plot_index] = list() self._fit_curves[plot_index] = list() for line, xd in enumerate(x_data): yd = y_data[line] pen_color = next(self._pen_colors[plot_index]) self._plot_curves[plot_index].append( dockwidget.plot_PlotWidget.plot( pen=mkColor(pen_color), symbol='d', symbolSize=6, symbolBrush=mkColor(pen_color))) self._plot_curves[plot_index][-1].setData(x=xd, y=yd) self._fit_curves[plot_index].append( dockwidget.plot_PlotWidget.plot()) self._fit_curves[plot_index][-1].setPen('r') @QtCore.Slot(int) @QtCore.Slot(int, dict) def update_plot_parameters(self, plot_index, params=None): """ Function updated limits, labels and units in the plot and parameter widgets. """ if not (0 <= plot_index < len(self._plot_dockwidgets)): self.log.warning( 'Tried to update plot with invalid index {0:d}'.format( plot_index)) return dockwidget = self._plot_dockwidgets[plot_index].widget() if params is None: params = dict() params['x_label'] = self._plot_logic.get_x_label(plot_index) params['y_label'] = self._plot_logic.get_y_label(plot_index) params['x_unit'] = self._plot_logic.get_x_unit(plot_index) params['y_unit'] = self._plot_logic.get_y_unit(plot_index) params['x_limits'] = self._plot_logic.get_x_limits(plot_index) params['y_limits'] = self._plot_logic.get_y_limits(plot_index) if 'x_label' in params or 'x_unit' in params: label = params.get('x_label', None) unit = params.get('x_unit', None) if label is None: label = self._plot_logic.get_x_label(plot_index) if unit is None: unit = self._plot_logic.get_x_unit(plot_index) dockwidget.plot_PlotWidget.setLabel('bottom', label, units=unit) dockwidget.x_label_lineEdit.blockSignals(True) dockwidget.x_unit_lineEdit.blockSignals(True) dockwidget.x_label_lineEdit.setText(label) dockwidget.x_unit_lineEdit.setText(unit) dockwidget.x_label_lineEdit.blockSignals(False) dockwidget.x_unit_lineEdit.blockSignals(False) if 'y_label' in params or 'y_unit' in params: label = params.get('y_label', None) unit = params.get('y_unit', None) if label is None: label = self._plot_logic.get_y_label(plot_index) if unit is None: unit = self._plot_logic.get_y_unit(plot_index) dockwidget.plot_PlotWidget.setLabel('left', label, units=unit) dockwidget.y_label_lineEdit.blockSignals(True) dockwidget.y_unit_lineEdit.blockSignals(True) dockwidget.y_label_lineEdit.setText(label) dockwidget.y_unit_lineEdit.setText(unit) dockwidget.y_label_lineEdit.blockSignals(False) dockwidget.y_unit_lineEdit.blockSignals(False) if 'x_limits' in params: limits = params['x_limits'] self._pg_signal_proxys[plot_index][0].block = True dockwidget.plot_PlotWidget.setXRange(*limits, padding=0) self._pg_signal_proxys[plot_index][0].block = False dockwidget.x_lower_limit_DoubleSpinBox.blockSignals(True) dockwidget.x_upper_limit_DoubleSpinBox.blockSignals(True) dockwidget.x_lower_limit_DoubleSpinBox.setValue(limits[0]) dockwidget.x_upper_limit_DoubleSpinBox.setValue(limits[1]) dockwidget.x_lower_limit_DoubleSpinBox.blockSignals(False) dockwidget.x_upper_limit_DoubleSpinBox.blockSignals(False) if 'y_limits' in params: limits = params['y_limits'] self._pg_signal_proxys[plot_index][1].block = True dockwidget.plot_PlotWidget.setYRange(*limits, padding=0) self._pg_signal_proxys[plot_index][1].block = False dockwidget.y_lower_limit_DoubleSpinBox.blockSignals(True) dockwidget.y_upper_limit_DoubleSpinBox.blockSignals(True) dockwidget.y_lower_limit_DoubleSpinBox.setValue(limits[0]) dockwidget.y_upper_limit_DoubleSpinBox.setValue(limits[1]) dockwidget.y_lower_limit_DoubleSpinBox.blockSignals(False) dockwidget.y_upper_limit_DoubleSpinBox.blockSignals(False) def save_clicked(self, plot_index): """ Handling the save button to save the data into a file. """ self._flush_pg_proxy(plot_index) self._plot_logic.save_data(plot_index=plot_index) def save_all_clicked(self): """ Handling the save button to save the data into a file. """ for plot_index, _ in enumerate(self._plot_dockwidgets): self.save_clicked(plot_index) def remove_clicked(self, plot_index): self._flush_pg_proxy(plot_index) self.sigRemovePlotClicked.emit(plot_index) def x_auto_range_clicked(self, plot_index): """ Set the parameter_1_x_limits to the min/max of the data values """ self.sigAutoRangeClicked.emit(plot_index, True, False) def y_auto_range_clicked(self, plot_index): """ Set the parameter_1_y_limits to the min/max of the data values """ self.sigAutoRangeClicked.emit(plot_index, False, True) def x_limits_changed(self, plot_index): """ Handling the change of the parameter_1_x_limits. """ dockwidget = self._plot_dockwidgets[plot_index].widget() self.sigPlotParametersChanged.emit( plot_index, { 'x_limits': [ dockwidget.x_lower_limit_DoubleSpinBox.value(), dockwidget.x_upper_limit_DoubleSpinBox.value() ] }) def y_limits_changed(self, plot_index): """ Handling the change of the parameter_1_y_limits. """ dockwidget = self._plot_dockwidgets[plot_index].widget() self.sigPlotParametersChanged.emit( plot_index, { 'y_limits': [ dockwidget.y_lower_limit_DoubleSpinBox.value(), dockwidget.y_upper_limit_DoubleSpinBox.value() ] }) def x_label_changed(self, plot_index): """ Set the x-label """ dockwidget = self._plot_dockwidgets[plot_index].widget() self.sigPlotParametersChanged.emit( plot_index, {'x_label': dockwidget.x_label_lineEdit.text()}) def y_label_changed(self, plot_index): """ Set the y-label and the uni of plot 1 """ dockwidget = self._plot_dockwidgets[plot_index].widget() self.sigPlotParametersChanged.emit( plot_index, {'y_label': dockwidget.y_label_lineEdit.text()}) def x_unit_changed(self, plot_index): """ Set the x-label """ dockwidget = self._plot_dockwidgets[plot_index].widget() self.sigPlotParametersChanged.emit( plot_index, {'x_unit': dockwidget.x_unit_lineEdit.text()}) def y_unit_changed(self, plot_index): """ Set the y-label and the uni of plot 1 """ dockwidget = self._plot_dockwidgets[plot_index].widget() self.sigPlotParametersChanged.emit( plot_index, {'y_unit': dockwidget.y_unit_lineEdit.text()}) def fit_clicked(self, plot_index=0): """ Triggers the fit to be done. Attention, this runs in the GUI thread. """ current_fit_method = self._plot_dockwidgets[plot_index].widget( ).fit_comboBox.getCurrentFit()[0] self.sigDoFit.emit(current_fit_method, plot_index) @QtCore.Slot(int, np.ndarray, str, str) def update_fit_data(self, plot_index, fit_data=None, formatted_fitresult=None, fit_method=None): """ Function that handles the fit results received from the logic via a signal. @param int plot_index: index of the plot the fit was performed for in the range for 0 to 2 @param 3-dimensional np.ndarray fit_data: the fit data in a 2-d array for each data set @param str formatted_fitresult: string containing the parameters already formatted @param str fit_method: the fit_method used """ dockwidget = self._plot_dockwidgets[plot_index].widget() if fit_data is None or formatted_fitresult is None or fit_method is None: fit_data, formatted_fitresult, fit_method = self._plot_logic.get_fit_data( plot_index) if not fit_method: fit_method = 'No Fit' dockwidget.fit_comboBox.blockSignals(True) dockwidget.show_fit_checkBox.setChecked(True) dockwidget.fit_textBrowser.clear() dockwidget.fit_comboBox.setCurrentFit(fit_method) if fit_method == 'No Fit': for index, curve in enumerate(self._fit_curves[plot_index]): if curve in dockwidget.plot_PlotWidget.items(): dockwidget.plot_PlotWidget.removeItem(curve) else: dockwidget.fit_textBrowser.setPlainText(formatted_fitresult) for index, curve in enumerate(self._fit_curves[plot_index]): if curve not in dockwidget.plot_PlotWidget.items(): dockwidget.plot_PlotWidget.addItem(curve) curve.setData(x=fit_data[index][0], y=fit_data[index][1]) dockwidget.fit_comboBox.blockSignals(False) def _pyqtgraph_x_limits_changed(self, plot_index, limits): plot_item = self._plot_dockwidgets[plot_index].widget( ).plot_PlotWidget.getPlotItem() if plot_item.ctrl.logXCheck.isChecked( ) or plot_item.ctrl.fftCheck.isChecked(): return self.sigPlotParametersChanged.emit(plot_index, {'x_limits': limits}) def _pyqtgraph_y_limits_changed(self, plot_index, limits): plot_item = self._plot_dockwidgets[plot_index].widget( ).plot_PlotWidget.getPlotItem() if plot_item.ctrl.logYCheck.isChecked( ) or plot_item.ctrl.fftCheck.isChecked(): return self.sigPlotParametersChanged.emit(plot_index, {'y_limits': limits}) def _flush_pg_proxy(self, plot_index): x_proxy, y_proxy = self._pg_signal_proxys[plot_index] x_proxy.flush() y_proxy.flush()
class WavemeterLogGui(GUIBase): _modclass = 'WavemeterLogGui' _modtype = 'gui' ## declare connectors wavemeterloggerlogic1 = Connector(interface='WavemeterLoggerLogic') savelogic = Connector(interface='SaveLogic') sigStartCounter = QtCore.Signal() sigStopCounter = QtCore.Signal() sigFitChanged = QtCore.Signal(str) sigDoFit = QtCore.Signal() def __init__(self, config, **kwargs): super().__init__(config=config, **kwargs) self.log.debug('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 and initialisation of the GUI. """ self._wm_logger_logic = self.get_connector('wavemeterloggerlogic1') self._save_logic = self.get_connector('savelogic') # setting up the window self._mw = WavemeterLogWindow() ## giving the plots names allows us to link their axes together self._pw = self._mw.plotWidget # pg.PlotWidget(name='Counter1') self._plot_item = self._pw.plotItem ## create a new ViewBox, link the right axis to its coordinate system self._right_axis = pg.ViewBox() self._plot_item.showAxis('right') self._plot_item.scene().addItem(self._right_axis) self._plot_item.getAxis('right').linkToView(self._right_axis) self._right_axis.setXLink(self._plot_item) ## create a new ViewBox, link the right axis to its coordinate system self._top_axis = pg.ViewBox() self._plot_item.showAxis('top') self._plot_item.scene().addItem(self._top_axis) self._plot_item.getAxis('top').linkToView(self._top_axis) self._top_axis.setYLink(self._plot_item) self._top_axis.invertX(b=True) # handle resizing of any of the elements self._update_plot_views() self._plot_item.vb.sigResized.connect(self._update_plot_views) self._pw.setLabel('left', 'Fluorescence', units='counts/s') self._pw.setLabel('right', 'Number of Points', units='#') self._pw.setLabel('bottom', 'Wavelength', units='nm') self._pw.setLabel('top', 'Relative Frequency', units='Hz') self._mw.actionStop_resume_scan.triggered.connect( self.stop_resume_clicked) self._mw.actionSave_histogram.triggered.connect(self.save_clicked) self._mw.actionStart_scan.triggered.connect(self.start_clicked) self._mw.actionAuto_range.triggered.connect(self.set_auto_range) # defining the parameters to edit self._mw.binSpinBox.setValue(self._wm_logger_logic.get_bins()) self._mw.binSpinBox.editingFinished.connect(self.recalculate_histogram) self._mw.minDoubleSpinBox.setValue( self._wm_logger_logic.get_min_wavelength()) self._mw.minDoubleSpinBox.editingFinished.connect( self.recalculate_histogram) self._mw.maxDoubleSpinBox.setValue( self._wm_logger_logic.get_max_wavelength()) self._mw.maxDoubleSpinBox.editingFinished.connect( self.recalculate_histogram) self._mw.show() ## Create an empty plot curve to be filled later, set its pen self.curve_data_points = pg.PlotDataItem(pen=pg.mkPen(palette.c1), symbol=None) self.curve_nm_counts = pg.PlotDataItem(pen=pg.mkPen( palette.c2, style=QtCore.Qt.DotLine), symbol=None) self.curve_hz_counts = pg.PlotDataItem(pen=pg.mkPen( palette.c6, style=QtCore.Qt.DotLine), symbol=None) self.curve_envelope = pg.PlotDataItem(pen=pg.mkPen( palette.c3, style=QtCore.Qt.DotLine), symbol=None) self.curve_fit = pg.PlotDataItem(pen=pg.mkPen(palette.c2, width=3), symbol=None) self._pw.addItem(self.curve_data_points) self._pw.addItem(self.curve_envelope) self._right_axis.addItem(self.curve_nm_counts) self._top_axis.addItem(self.curve_hz_counts) # scatter plot for time series self._spw = self._mw.scatterPlotWidget self._spi = self._spw.plotItem self._spw.setLabel('bottom', 'Wavelength', units='nm') self._spw.setLabel('left', 'Time', units='s') self._scatterplot = pg.ScatterPlotItem(size=10, pen=pg.mkPen(None), brush=pg.mkBrush( 255, 255, 255, 20)) self._spw.addItem(self._scatterplot) self._spw.setXLink(self._plot_item) self._wm_logger_logic.sig_new_data_point.connect(self.add_data_point) self._wm_logger_logic.sig_data_updated.connect(self.updateData) # fit settings self._fsd = FitSettingsDialog(self._wm_logger_logic.fc) self._fsd.sigFitsUpdated.connect( self._mw.fit_methods_ComboBox.setFitFunctions) self._fsd.applySettings() self._mw.actionFit_settings.triggered.connect(self._fsd.show) self._mw.do_fit_PushButton.clicked.connect(self.doFit) self.sigDoFit.connect(self._wm_logger_logic.do_fit) self.sigFitChanged.connect(self._wm_logger_logic.fc.set_current_fit) self._wm_logger_logic.sig_fit_updated.connect(self.updateFit) def on_deactivate(self): """ Deactivate the module properly. """ 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 updateData(self): """ The function that grabs the data and sends it to the plot. """ self._mw.wavelengthLabel.setText('{0:,.6f} nm '.format( self._wm_logger_logic.current_wavelength)) self._mw.autoMinLabel.setText('Minimum: {0:3.6f} (nm) '.format( self._wm_logger_logic.intern_xmin)) self._mw.autoMaxLabel.setText('Maximum: {0:3.6f} (nm) '.format( self._wm_logger_logic.intern_xmax)) x_axis = self._wm_logger_logic.histogram_axis x_axis_hz = (3.0e17 / (x_axis) - 6.0e17 / (self._wm_logger_logic.get_max_wavelength() + self._wm_logger_logic.get_min_wavelength())) plotdata = np.array(self._wm_logger_logic.counts_with_wavelength) if len(plotdata.shape) > 1 and plotdata.shape[1] == 3: self.curve_data_points.setData(plotdata[:, 2:0:-1]) self.curve_nm_counts.setData(x=x_axis, y=self._wm_logger_logic.histogram) self.curve_hz_counts.setData(x=x_axis_hz, y=self._wm_logger_logic.histogram) self.curve_envelope.setData(x=x_axis, y=self._wm_logger_logic.envelope_histogram) @QtCore.Slot() def doFit(self): self.sigFitChanged.emit( self._mw.fit_methods_ComboBox.getCurrentFit()[0]) self.sigDoFit.emit() @QtCore.Slot() def updateFit(self): """ Do the configured fit and show it in the plot """ fit_name = self._wm_logger_logic.fc.current_fit fit_result = self._wm_logger_logic.fc.current_fit_result fit_param = self._wm_logger_logic.fc.current_fit_param if fit_result is not None: # display results as formatted text self._mw.fit_results_DisplayWidget.clear() try: formated_results = units.create_formatted_output( fit_result.result_str_dict) except: formated_results = 'this fit does not return formatted results' self._mw.fit_results_DisplayWidget.setPlainText(formated_results) if fit_name is not None: self._mw.fit_methods_ComboBox.setCurrentFit(fit_name) # check which fit method is used and show the curve in the plot accordingly if fit_name != 'No Fit': self.curve_fit.setData(x=self._wm_logger_logic.wlog_fit_x, y=self._wm_logger_logic.wlog_fit_y) if self.curve_fit not in self._mw.plotWidget.listDataItems(): self._mw.plotWidget.addItem(self.curve_fit) else: if self.curve_fit in self._mw.plotWidget.listDataItems(): self._mw.plotWidget.removeItem(self.curve_fit) def add_data_point(self, point): if len(point) >= 3: spts = [{ 'pos': (point[0], point[1]), 'size': 5, 'brush': pg.intColor(point[2] / 100, 255) }] self._scatterplot.addPoints(spts) def stop_resume_clicked(self): """ Handling the Start button to stop and restart the counter. """ # If running, then we stop the measurement and enable inputs again if self._wm_logger_logic.module_state() == 'running': self._mw.actionStop_resume_scan.setText('Resume') self._wm_logger_logic.stop_scanning() self._mw.actionStop_resume_scan.setEnabled(True) self._mw.actionStart_scan.setEnabled(True) self._mw.binSpinBox.setEnabled(True) # Otherwise, we start a measurement and disable some inputs. else: self._mw.actionStop_resume_scan.setText('Stop') self._wm_logger_logic.start_scanning(resume=True) self._mw.actionStart_scan.setEnabled(False) self._mw.binSpinBox.setEnabled(False) def start_clicked(self): """ Handling resume of the scanning without resetting the data. """ if self._wm_logger_logic.module_state() == 'idle': self._scatterplot.clear() self._wm_logger_logic.start_scanning() # Enable the stop button once a scan starts. self._mw.actionStop_resume_scan.setText('Stop') self._mw.actionStop_resume_scan.setEnabled(True) self._mw.actionStart_scan.setEnabled(False) self._mw.binSpinBox.setEnabled(False) self.recalculate_histogram() else: self.log.error('Cannot scan, since a scan is alredy running.') def save_clicked(self): """ Handling the save button to save the data into a file. """ timestamp = datetime.datetime.now() filepath = self._save_logic.get_path_for_module( module_name='WavemeterLogger') filename = os.path.join( filepath, timestamp.strftime('%Y%m%d-%H%M-%S_wavemeter_log_thumbnail')) exporter = pg.exporters.SVGExporter(self._pw.plotItem) exporter.export(filename + '.svg') self._wm_logger_logic.save_data(timestamp=timestamp) def recalculate_histogram(self): self._wm_logger_logic.recalculate_histogram( bins=self._mw.binSpinBox.value(), xmin=self._mw.minDoubleSpinBox.value(), xmax=self._mw.maxDoubleSpinBox.value()) def set_auto_range(self): self._mw.minDoubleSpinBox.setValue(self._wm_logger_logic.intern_xmin) self._mw.maxDoubleSpinBox.setValue(self._wm_logger_logic.intern_xmax) self.recalculate_histogram() ## Handle view resizing def _update_plot_views(self): ## view has resized; update auxiliary views to match self._right_axis.setGeometry(self._plot_item.vb.sceneBoundingRect()) self._top_axis.setGeometry(self._plot_item.vb.sceneBoundingRect()) ## need to re-update linked axes since this was called ## incorrectly while views had different shapes. ## (probably this should be handled in ViewBox.resizeEvent) self._right_axis.linkedViewChanged(self._plot_item.vb, self._right_axis.XAxis) self._top_axis.linkedViewChanged(self._plot_item.vb, self._top_axis.YAxis)
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 SpectrometerGui(GUIBase): _modclass = 'SpectrometerGui' _modtype = 'gui' # declare connectors spectrumlogic = Connector(interface='SpectrumLogic') def __init__(self, config, **kwargs): super().__init__(config=config, **kwargs) def on_activate(self): """ Definition and initialisation of the GUI. """ self._spectrum_logic = self.spectrumlogic() # setting up the window self._mw = SpectrometerWindow() self._mw.stop_diff_spec_Action.setEnabled(False) self._mw.resume_diff_spec_Action.setEnabled(False) self._mw.correct_background_Action.setChecked( self._spectrum_logic.background_correction) # giving the plots names allows us to link their axes together self._pw = self._mw.plotWidget # pg.PlotWidget(name='Counter1') self._plot_item = self._pw.plotItem # create a new ViewBox, link the right axis to its coordinate system self._right_axis = pg.ViewBox() self._plot_item.showAxis('right') self._plot_item.scene().addItem(self._right_axis) self._plot_item.getAxis('right').linkToView(self._right_axis) self._right_axis.setXLink(self._plot_item) # create a new ViewBox, link the right axis to its coordinate system self._top_axis = pg.ViewBox() self._plot_item.showAxis('top') self._plot_item.scene().addItem(self._top_axis) self._plot_item.getAxis('top').linkToView(self._top_axis) self._top_axis.setYLink(self._plot_item) self._top_axis.invertX(b=True) # handle resizing of any of the elements self._pw.setLabel('left', 'Fluorescence', units='counts/s') self._pw.setLabel('right', 'Number of Points', units='#') self._pw.setLabel('bottom', 'Wavelength', units='m') self._pw.setLabel('top', 'Relative Frequency', units='Hz') # Create an empty plot curve to be filled later, set its pen self._curve1 = self._pw.plot() self._curve1.setPen(palette.c1, width=2) self._curve2 = self._pw.plot() self._curve2.setPen(palette.c2, width=2) self.update_data() # Connect singals self._mw.rec_single_spectrum_Action.triggered.connect( self.record_single_spectrum) self._mw.start_diff_spec_Action.triggered.connect( self.start_differential_measurement) self._mw.stop_diff_spec_Action.triggered.connect( self.stop_differential_measurement) self._mw.resume_diff_spec_Action.triggered.connect( self.resume_differential_measurement) self._mw.save_spectrum_Action.triggered.connect( self.save_spectrum_data) self._mw.correct_background_Action.triggered.connect( self.correct_background) self._mw.acquire_background_Action.triggered.connect( self.acquire_background) self._mw.save_background_Action.triggered.connect( self.save_background_data) self._mw.restore_default_view_Action.triggered.connect( self.restore_default_view) self._spectrum_logic.sig_specdata_updated.connect(self.update_data) self._spectrum_logic.spectrum_fit_updated_Signal.connect( self.update_fit) self._spectrum_logic.fit_domain_updated_Signal.connect( self.update_fit_domain) self._mw.show() self._save_PNG = True # Internal user input changed signals self._mw.fit_domain_min_doubleSpinBox.valueChanged.connect( self.set_fit_domain) self._mw.fit_domain_max_doubleSpinBox.valueChanged.connect( self.set_fit_domain) # Internal trigger signals self._mw.do_fit_PushButton.clicked.connect(self.do_fit) self._mw.fit_domain_all_data_pushButton.clicked.connect( self.reset_fit_domain_all_data) # fit settings self._fsd = FitSettingsDialog(self._spectrum_logic.fc) self._fsd.sigFitsUpdated.connect( self._mw.fit_methods_ComboBox.setFitFunctions) self._fsd.applySettings() self._mw.action_FitSettings.triggered.connect(self._fsd.show) def on_deactivate(self): """ Deinitialisation performed during deactivation of the module. """ # disconnect signals self._fsd.sigFitsUpdated.disconnect() 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 update_data(self): """ The function that grabs the data and sends it to the plot. """ data = self._spectrum_logic.spectrum_data # erase previous fit line self._curve2.setData(x=[], y=[]) # draw new data self._curve1.setData(x=data[0, :], y=data[1, :]) def update_fit(self, fit_data, result_str_dict, current_fit): """ Update the drawn fit curve and displayed fit results. """ if current_fit != 'No Fit': # display results as formatted text self._mw.spectrum_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.spectrum_fit_results_DisplayWidget.setPlainText( formated_results) # redraw the fit curve in the GUI plot. self._curve2.setData(x=fit_data[0, :], y=fit_data[1, :]) def record_single_spectrum(self): """ Handle resume of the scanning without resetting the data. """ self._spectrum_logic.get_single_spectrum() def start_differential_measurement(self): # Change enabling of GUI actions self._mw.stop_diff_spec_Action.setEnabled(True) self._mw.start_diff_spec_Action.setEnabled(False) self._mw.rec_single_spectrum_Action.setEnabled(False) self._mw.resume_diff_spec_Action.setEnabled(False) self._spectrum_logic.start_differential_spectrum() def stop_differential_measurement(self): self._spectrum_logic.stop_differential_spectrum() # Change enabling of GUI actions self._mw.stop_diff_spec_Action.setEnabled(False) self._mw.start_diff_spec_Action.setEnabled(True) self._mw.rec_single_spectrum_Action.setEnabled(True) self._mw.resume_diff_spec_Action.setEnabled(True) def resume_differential_measurement(self): self._spectrum_logic.resume_differential_spectrum() # Change enabling of GUI actions self._mw.stop_diff_spec_Action.setEnabled(True) self._mw.start_diff_spec_Action.setEnabled(False) self._mw.rec_single_spectrum_Action.setEnabled(False) self._mw.resume_diff_spec_Action.setEnabled(False) def save_spectrum_data(self): self._spectrum_logic.save_spectrum_data() def correct_background(self): self._spectrum_logic.background_correction = self._mw.correct_background_Action.isChecked( ) def acquire_background(self): self._spectrum_logic.get_single_spectrum(background=True) def save_background_data(self): self._spectrum_logic.save_spectrum_data(background=True) def do_fit(self): """ Command spectrum logic to do the fit with the chosen fit function. """ fit_function = self._mw.fit_methods_ComboBox.getCurrentFit()[0] self._spectrum_logic.do_fit(fit_function) def set_fit_domain(self): """ Set the fit domain in the spectrum logic to values given by the GUI spinboxes. """ lambda_min = self._mw.fit_domain_min_doubleSpinBox.value() lambda_max = self._mw.fit_domain_max_doubleSpinBox.value() new_fit_domain = np.array([lambda_min, lambda_max]) self._spectrum_logic.set_fit_domain(new_fit_domain) def reset_fit_domain_all_data(self): """ Reset the fit domain to match the full data set. """ self._spectrum_logic.set_fit_domain() def update_fit_domain(self, domain): """ Update the displayed fit domain to new values (set elsewhere). """ self._mw.fit_domain_min_doubleSpinBox.setValue(domain[0]) self._mw.fit_domain_max_doubleSpinBox.setValue(domain[1]) def restore_default_view(self): """ Restore the arrangement of DockWidgets to the default """ # Show any hidden dock widgets self._mw.spectrum_fit_dockWidget.show() # re-dock any floating dock widgets self._mw.spectrum_fit_dockWidget.setFloating(False) # Arrange docks widgets self._mw.addDockWidget( QtCore.Qt.DockWidgetArea(QtCore.Qt.TopDockWidgetArea), self._mw.spectrum_fit_dockWidget) # Set the toolbar to its initial top area self._mw.addToolBar(QtCore.Qt.TopToolBarArea, self._mw.measure_ToolBar) self._mw.addToolBar(QtCore.Qt.TopToolBarArea, self._mw.background_ToolBar) self._mw.addToolBar(QtCore.Qt.TopToolBarArea, self._mw.differential_ToolBar) return 0
class M2ScannerGUI(GUIBase): _modclass = 'M2LaserGui' _modtype = 'gui' sigStartCounter = QtCore.Signal() sigStopCounter = QtCore.Signal() sigFitChanged = QtCore.Signal(str) sigDoFit = QtCore.Signal() # declare connectors laserlogic = Connector(interface='M2LaserLogic') def __init__(self, config, **kwargs): super().__init__(config=config, **kwargs) def on_activate(self): """ Definition and initialisation of the GUI. """ #Connect to laser_logic self._laser_logic = self.laserlogic() # setting up the window self._mw = M2ControllerWindow() ################### # Connect Signals ################### self._mw.run_scan_Action.triggered.connect( self.start_clicked) # start_clicked then triggers sigStartCounter self._mw.save_scan_Action.triggered.connect( self.save_spectrum_data ) # start_clicked then triggers sigStartCounter self._mw.actionSave_as.triggered.connect(self.change_filepath) # self._mw.restore_default_view_Action.triggered.connect(self.restore_default_view) # FROM countergui.py # Connect signals for counter self.sigStartCounter.connect(self._laser_logic.startCount) self.sigStopCounter.connect(self._laser_logic.stopCount) self._laser_logic.sigScanComplete.connect( self.scanComplete) #handle the end of scans # Handling signals from the logic # signals during terascan self._laser_logic.sigCounterUpdated.connect(self.update_data) # update wavelength reading and status reading only self._laser_logic.sigUpdate.connect(self.updateGui) #################### #set up GUI #################### self._mw.scanType_comboBox.setInsertPolicy = 6 # InsertAlphabetically self._mw.scanType_comboBox.addItem("Fine") self._mw.scanType_comboBox.addItem("Medium") self._mw.scanRate_comboBox.setInsertPolicy = 6 #InsertAlphabetically for x in range(0, 14): self._mw.scanRate_comboBox.addItem(str(x)) ##################### # Connecting user interactions ######################## self._mw.scanType_comboBox.currentIndexChanged.connect( self.update_calculated_scan_params) self._mw.scanRate_comboBox.currentIndexChanged.connect( self.update_calculated_scan_params) self._mw.startWvln_doubleSpinBox.valueChanged.connect( self.update_calculated_scan_params) self._mw.stopWvln_doubleSpinBox.valueChanged.connect( self.update_calculated_scan_params) self._mw.numScans_spinBox.valueChanged.connect( self.update_calculated_scan_params) self._mw.plotPoints_checkBox.stateChanged.connect( self.update_points_checkbox) self._mw.replot_pushButton.clicked.connect(self.replot_pressed) #below from countergui.py self._pw = self._mw.plotWidget self._plot_item = self._pw.plotItem # create a new ViewBox, link the right axis to its coordinate system self._right_axis = pg.ViewBox() self._plot_item.showAxis('right') self._plot_item.scene().addItem(self._right_axis) self._plot_item.getAxis('right').linkToView(self._right_axis) self._right_axis.setXLink(self._plot_item) # create a new ViewBox, link the right axis to its coordinate system self._top_axis = pg.ViewBox() self._plot_item.showAxis('top') self._plot_item.scene().addItem(self._top_axis) self._plot_item.getAxis('top').linkToView(self._top_axis) self._top_axis.setYLink(self._plot_item) self._top_axis.invertX(b=True) self._pw.setLabel('left', 'Count Rate', units='counts/s') ##self._pw.setLabel('right', 'Counts', units='#') #Tget rid of or implement self._pw.setLabel('bottom', 'Wavelength', units='nm') ##self._pw.setLabel('top', 'Relative Frequency', units='Hz') #TODO implement # Create an empty plot curve to be filled later, set its pen self._curve1 = self._pw.plot() self._curve1.setPen(palette.c1, width=2) #self._curve1 = pg.PlotDataItem( # pen=pg.mkPen(palette.c3, style=QtCore.Qt.DotLine), # symbol='s', # symbolPen=palette.c3, # symbolBrush=palette.c3, # symbolSize=5) self._pw.addItem(self._curve1) #initialize starting calculated scanning parameters self.update_calculated_scan_params() # initialize # self._mw.wvlnRead_disp.setText("Laser Connected") self._mw.status_disp.setText("idle") #show the main gui self._mw.show() # fit settings #just added! self._fsd = FitSettingsDialog(self._laser_logic.fc) self._fsd.sigFitsUpdated.connect( self._mw.fit_methods_ComboBox.setFitFunctions) self._fsd.applySettings() self._mw.action_FitSettings.triggered.connect(self._fsd.show) self._mw.do_fit_PushButton.clicked.connect(self.doFit) self.sigDoFit.connect(self._laser_logic.do_fit) self.sigFitChanged.connect(self._laser_logic.fc.set_current_fit) self._laser_logic.sig_fit_updated.connect(self.updateFit) #TODO self.curve_fit = pg.PlotDataItem(pen=pg.mkPen(palette.c2, width=3), symbol=None) def on_deactivate(self): """ Deinitialisation performed during deactivation of the module. """ # disconnect signals #I think this will be unnecessary given that only the gui, #not hardware or logic modules will deactivate when deactivate button is pressed #(modeled after previously existing Laser module) # self._fsd.sigFitsUpdated.disconnect() print('in gui trying to deactivate') self._mw.close() #if a terascan is running, stop the terascan before deactivating if self._laser_logic.module_state() == 'locked': print('Terascan is running. Trying to stop now!') self._mw.run_scan_Action.setText('Start counter') self.sigStopCounter.emit() startWvln, stopWvln, scantype, scanrate, numScans = self.get_scan_info( ) self._laser_logic._laser.stop_terascan(scantype, True) 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 update_data(self): """ The function that grabs the terascan count data and sends it to the plot. """ # print('update_data called in gui') ################ Adapted from spectrometer gui data = self._laser_logic.countdata #Don't plot initialization 0's mask = (data == 0).all(0) start_idx = np.argmax(~mask) data = data[:, start_idx:] # draw new data if data.shape[1] > 0: self._curve1.setData(x=data[0, :], y=data[1, :]) # print('updatedata finished in gui') def save_spectrum_data(self): self._laser_logic.save_spectrum_data() def get_scan_info(self): # print('get_scan_info called in gui') finerates = [ 20, 10, 5, 2, 1, .5, .2, .1, .05, .02, .01, .005, .002, .001 ] # in GHz/s mediumrates = [100, 50, 20, 15, 10, 5, 2, 1] # in GHz/s typebox = self._mw.scanType_comboBox.currentText() if typebox == 'Fine': scanrate = finerates[int( self._mw.scanRate_comboBox.currentText())] * 10**9 # in Hz/s else: indx = int(self._mw.scanRate_comboBox.currentText()) #handle if index is too high for medium scan if indx >= len(mediumrates): error_dialog = QtWidgets.QErrorMessage() error_dialog.showMessage( 'ERROR: index given for medium rates is too high') error_dialog.exec() self._mw.scanRate_comboBox.setCurrentIndex(7) return #alternatively (Todo?) change number of scanRate options based on whether we are on Fine or Medium else: scanrate = mediumrates[int(self._mw.scanRate_comboBox. currentText())] * 10**9 # in Hz/s startwvln = self._mw.startWvln_doubleSpinBox.value() * 10**-9 # in m stopwvln = self._mw.stopWvln_doubleSpinBox.value() * 10**-9 #in m numscans = self._mw.numScans_spinBox.value() return startwvln, stopwvln, typebox.lower(), scanrate, numscans def update_calculated_scan_params(self): speed_c = 299792458 #speed of light in m/s try: startWvln, stopWvln, scantype, scanrate, numScans = self.get_scan_info( ) except: return #error handling occurs inside get_scan_info midWvln = (stopWvln + startWvln) / 2 #in m rangeWvln = (stopWvln - startWvln) #in m startFreq = speed_c / stopWvln #in Hz stopFreq = speed_c / startWvln #in Hz midFreq = (stopFreq + startFreq) / 2 #in Hz rangeFreq = stopFreq - startFreq #in Hz scanrate_wvln = scanrate * speed_c / (midFreq**2) #in m/s self._mw.calcDwellTime_disp.setText("0.2 sec \n{0:.4f} pm".format( 0.2 * scanrate_wvln * 10**12)) #Scan resolution is 0.2 sec (based on manually testing, with print #and time.time() statements in countloop). May be different on a different computer self._mw.calcScanRes_disp.setText( "{0:.3f} GHz/s \n{1:.3f} pm/s".format(scanrate * 10**-9, scanrate_wvln * 10**12)) totaltime = numScans * rangeFreq / scanrate secs = totaltime % 60 mins = totaltime // 60 hrs = mins // 60 days = hrs // 24 if days > 0: timestr = "{0:.0f} days, {1:.0f} hrs, {2:.0f} min, {3:.0f} sec".format( days, hrs, mins, secs) elif hrs > 0: timestr = "{0:.0f} hrs, {1:.0f} min, {2:.0f} sec".format( hrs, mins, secs) elif mins > 0: timestr = "{0:.0f} min, {1:.0f} sec".format(mins, secs) else: timestr = "{0:.0f} sec".format(secs) self._mw.calcTotalTime_disp.setText(timestr) @QtCore.Slot() def updateGui(self): """ Update labels, the plot and button states with new data. """ self._mw.wvlnRead_disp.setText("{0:.5f}".format( self._laser_logic.current_wavelength)) self._mw.status_disp.setText(self._laser_logic.current_state) def start_clicked( self ): #todo: move the logic elements of this function to the logic module """ Handling the Start button to stop and restart the counter. """ # print('start_clicked called in gui') if self._laser_logic.module_state() == 'locked': print('STOP TERASCAN') # Disable/enable buttons as appropriate, update gui self._mw.replot_pushButton.setEnabled(True) self._mw.run_scan_Action.setEnabled(False) self._mw.status_disp.setText( 'stopping scan') #is not being seen.? fixme self._laser_logic.current_state = 'stopping scan' #is not being seen fixme self._mw.run_scan_Action.setText( 'Start counter' ) #this also isn't working as far as I can tell fixme # Stop the counter self.sigStopCounter.emit() # Enable the "start/stop scan" button Todo maybe wait for signal before enable? self._mw.run_scan_Action.setEnabled(True) else: print('START TERASCAN') # Disable/enable buttons as appropriate, update gui #self._laser_logic.current_state = 'starting scan' self._mw.status_disp.setText('starting scan') self._mw.replot_pushButton.setEnabled(False) self._mw.run_scan_Action.setEnabled(False) self._mw.run_scan_Action.setText( 'Stop counter') #not sure if this is working fixme # Grab terascan parameters startWvln, stopWvln, scantype, scanrate, numScans = self.get_scan_info( ) # More update gui self._mw.scanNumber_label.setText( str(self._laser_logic.repetition_count)) self._mw.scanNumber_label.setText("Scan {0:d} of {1:d}".format( self._laser_logic.repetition_count + 1, numScans)) # Check for input parameter errors. E.G., stop_wavelength should be less than start_wavelength if startWvln >= stopWvln: error_dialog = QtWidgets.QErrorMessage() error_dialog.showMessage( 'ERROR: start wavelength must be less than stop wavelength' ) error_dialog.exec() return self._laser_logic.module_state() # save terascan parameters to laser module self._laser_logic.scanParams = { "scanbounds": (startWvln, stopWvln), "scantype": scantype, "scanrate": scanrate, "numScans": numScans } # Start the counter self.sigStartCounter.emit() # Enable clicking of "start/stop" button self._mw.run_scan_Action.setEnabled(True) # print('start_clicked finished in gui') return self._laser_logic.module_state() def update_points_checkbox(self): #Change display style of counts plot #check if locked? if not self._mw.plotPoints_checkBox.isChecked(): self._curve1.setPen(palette.c1, width=2) self._curve1.setSymbol(None) else: self._curve1.setPen(palette.c3, style=QtCore.Qt.DotLine) self._curve1.setSymbol('s') self._curve1.setSymbolBrush(palette.c3) self._curve1.setSymbolSize(5) self._curve1.setSymbolPen(palette.c3) #self._pw.addItem(self._curve1) def replot_pressed(self): #Replot counts to be ordered by wavelength if self._laser_logic.module_state() == 'locked': pass #Button should be disabled when module_state is locked, so this should never happen anyway else: self._laser_logic.order_data() self.update_data() ### self._mw.replot_pushButton.setDefault(False) #Isn't working? Supposed to reset button style TODO fix def scanComplete(self): #Handle the end of scans startWvln, stopWvln, scantype, scanrate, numScans = self.get_scan_info( ) self._mw.wvlnRead_disp.setText("Scan Completed") self._mw.status_disp.setText("idle") if numScans != 1: # don't automatically save if it's just one scan #save scan. Note: save_spectrum_data increases repetition_count by 1 self._laser_logic.save_spectrum_data() if numScans == self._laser_logic.repetition_count or self._laser_logic.repetition_count == 0: # Final scan has been completed. self._laser_logic.repetition_count = 0 self._mw.replot_pushButton.setEnabled(True) self._laser_logic.module_state.unlock() else: # Advance to next scan self._mw.scanNumber_label.setText("Scan {0:d} of {1:d}".format( self._laser_logic.repetition_count + 1, numScans)) self._laser_logic.module_state.unlock() self.sigStartCounter.emit() #clears out data, etc. def change_filepath(self): save_dialog = QtWidgets.QFileDialog() self._laser_logic.filepath = save_dialog.getExistingDirectory() print('Saving in ' + self._laser_logic.filepath) self.save_spectrum_data() @QtCore.Slot() def doFit(self): self.sigFitChanged.emit( self._mw.fit_methods_ComboBox.getCurrentFit()[0]) self.sigDoFit.emit() @QtCore.Slot() def updateFit(self): """ Do the configured fit and show it in the plot """ fit_name = self._laser_logic.fc.current_fit fit_result = self._laser_logic.fc.current_fit_result fit_param = self._laser_logic.fc.current_fit_param if fit_result is not None: # display results as formatted text self._mw.fit_results_DisplayWidget.clear() try: formated_results = units.create_formatted_output( fit_result.result_str_dict) except: formated_results = 'this fit does not return formatted results' self._mw.fit_results_DisplayWidget.setPlainText(formated_results) if fit_name is not None: self._mw.fit_methods_ComboBox.setCurrentFit(fit_name) # check which fit method is used and show the curve in the plot accordingly if fit_name != 'No Fit': self.curve_fit.setData(x=self._laser_logic.wlog_fit_x, y=self._laser_logic.wlog_fit_y) if self.curve_fit not in self._mw.plotWidget.listDataItems(): self._mw.plotWidget.addItem(self.curve_fit) else: if self.curve_fit in self._mw.plotWidget.listDataItems(): self._mw.plotWidget.removeItem(self.curve_fit) #https://doc.qt.io/qt-5/signalsandslots.html #When a signal is emitted, the slots connected to it are usually executed immediately, just like a normal # function call. When this happens, the signals and slots mechanism is totally independent of any GUI event loop. # Execution of the code following the emit statement will occur once all slots have returned. The situation # is slightly different when using queued connections; in such a case, the code following the emit keyword # will continue immediately, and the slots will be executed later. #Seems wrong??? sigStartQueryLoop is connected NOT through a queued connection and the function doesn't wait #for the slots to complete. "slot returning" must just mean that the slot has begun executing???
class WavemeterLogGui(GUIBase): _modclass = 'WavemeterLogGui' _modtype = 'gui' ## declare connectors wavemeterloggerlogic1 = Connector(interface='WavemeterLoggerLogic') savelogic = Connector(interface='SaveLogic') sigStartCounter = QtCore.Signal() sigStopCounter = QtCore.Signal() sigFitChanged = QtCore.Signal(str) sigDoFit = QtCore.Signal() def __init__(self, config, **kwargs): super().__init__(config=config, **kwargs) self.log.debug('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 and initialisation of the GUI. """ self._wm_logger_logic = self.wavemeterloggerlogic1() self._save_logic = self.savelogic() # setting up the window self._mw = WavemeterLogWindow() ## giving the plots names allows us to link their axes together self._pw = self._mw.plotWidget # pg.PlotWidget(name='Counter1') self._plot_item = self._pw.plotItem ## create a new ViewBox, link the right axis to its coordinate system self._right_axis = pg.ViewBox() self._plot_item.showAxis('right') self._plot_item.scene().addItem(self._right_axis) self._plot_item.getAxis('right').linkToView(self._right_axis) self._right_axis.setXLink(self._plot_item) ## create a new ViewBox, link the right axis to its coordinate system self._top_axis = pg.ViewBox() self._plot_item.showAxis('top') self._plot_item.scene().addItem(self._top_axis) self._plot_item.getAxis('top').linkToView(self._top_axis) self._top_axis.setYLink(self._plot_item) self._top_axis.invertX(b=True) # handle resizing of any of the elements self._update_plot_views() self._plot_item.vb.sigResized.connect(self._update_plot_views) self._pw.setLabel('left', 'Fluorescence', units='counts/s') self._pw.setLabel('right', 'Number of Points', units='#') self._pw.setLabel('bottom', 'Wavelength', units='nm') self._pw.setLabel('top', 'Relative Frequency', units='Hz') self._mw.actionStop_resume_scan.triggered.connect(self.stop_resume_clicked) self._mw.actionSave_histogram.triggered.connect(self.save_clicked) self._mw.actionStart_scan.triggered.connect(self.start_clicked) self._mw.actionAuto_range.triggered.connect(self.set_auto_range) # defining the parameters to edit self._mw.binSpinBox.setValue(self._wm_logger_logic.get_bins()) self._mw.binSpinBox.editingFinished.connect(self.recalculate_histogram) self._mw.minDoubleSpinBox.setValue(self._wm_logger_logic.get_min_wavelength()) self._mw.minDoubleSpinBox.editingFinished.connect(self.recalculate_histogram) self._mw.maxDoubleSpinBox.setValue(self._wm_logger_logic.get_max_wavelength()) self._mw.maxDoubleSpinBox.editingFinished.connect(self.recalculate_histogram) self._mw.show() ## Create an empty plot curve to be filled later, set its pen self.curve_data_points = pg.PlotDataItem( pen=pg.mkPen(palette.c1), symbol=None ) self.curve_nm_counts = pg.PlotDataItem( pen=pg.mkPen(palette.c2, style=QtCore.Qt.DotLine), symbol=None ) self.curve_hz_counts = pg.PlotDataItem( pen=pg.mkPen(palette.c6, style=QtCore.Qt.DotLine), symbol=None ) self.curve_envelope = pg.PlotDataItem( pen=pg.mkPen(palette.c3, style=QtCore.Qt.DotLine), symbol=None ) self.curve_fit = pg.PlotDataItem( pen=pg.mkPen(palette.c2, width=3), symbol=None ) self._pw.addItem(self.curve_data_points) self._pw.addItem(self.curve_envelope) self._right_axis.addItem(self.curve_nm_counts) self._top_axis.addItem(self.curve_hz_counts) # scatter plot for time series self._spw = self._mw.scatterPlotWidget self._spi = self._spw.plotItem self._spw.setLabel('bottom', 'Wavelength', units='nm') self._spw.setLabel('left', 'Time', units='s') self._scatterplot = pg.ScatterPlotItem(size=10, pen=pg.mkPen(None), brush=pg.mkBrush(255, 255, 255, 20)) self._spw.addItem(self._scatterplot) self._spw.setXLink(self._plot_item) self._wm_logger_logic.sig_new_data_point.connect(self.add_data_point) self._wm_logger_logic.sig_data_updated.connect(self.updateData) # fit settings self._fsd = FitSettingsDialog(self._wm_logger_logic.fc) self._fsd.sigFitsUpdated.connect(self._mw.fit_methods_ComboBox.setFitFunctions) self._fsd.applySettings() self._mw.actionFit_settings.triggered.connect(self._fsd.show) self._mw.do_fit_PushButton.clicked.connect(self.doFit) self.sigDoFit.connect(self._wm_logger_logic.do_fit) self.sigFitChanged.connect(self._wm_logger_logic.fc.set_current_fit) self._wm_logger_logic.sig_fit_updated.connect(self.updateFit) def on_deactivate(self): """ Deactivate the module properly. """ 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 updateData(self): """ The function that grabs the data and sends it to the plot. """ self._mw.wavelengthLabel.setText('{0:,.6f} nm '.format(self._wm_logger_logic.current_wavelength)) self._mw.autoMinLabel.setText('Minimum: {0:3.6f} (nm) '.format(self._wm_logger_logic.intern_xmin)) self._mw.autoMaxLabel.setText('Maximum: {0:3.6f} (nm) '.format(self._wm_logger_logic.intern_xmax)) x_axis = self._wm_logger_logic.histogram_axis x_axis_hz = ( 3.0e17 / x_axis - 6.0e17 / (self._wm_logger_logic.get_max_wavelength() + self._wm_logger_logic.get_min_wavelength()) ) plotdata = np.array(self._wm_logger_logic.counts_with_wavelength) if len(plotdata.shape) > 1 and plotdata.shape[1] == 3: self.curve_data_points.setData(plotdata[:, 2:0:-1]) self.curve_nm_counts.setData(x=x_axis, y=self._wm_logger_logic.histogram) self.curve_hz_counts.setData(x=x_axis_hz, y=self._wm_logger_logic.histogram) self.curve_envelope.setData(x=x_axis, y=self._wm_logger_logic.envelope_histogram) @QtCore.Slot() def doFit(self): self.sigFitChanged.emit(self._mw.fit_methods_ComboBox.getCurrentFit()[0]) self.sigDoFit.emit() @QtCore.Slot() def updateFit(self): """ Do the configured fit and show it in the plot """ fit_name = self._wm_logger_logic.fc.current_fit fit_result = self._wm_logger_logic.fc.current_fit_result fit_param = self._wm_logger_logic.fc.current_fit_param if fit_result is not None: # display results as formatted text self._mw.fit_results_DisplayWidget.clear() try: formated_results = units.create_formatted_output(fit_result.result_str_dict) except: formated_results = 'this fit does not return formatted results' self._mw.fit_results_DisplayWidget.setPlainText(formated_results) if fit_name is not None: self._mw.fit_methods_ComboBox.setCurrentFit(fit_name) # check which fit method is used and show the curve in the plot accordingly if fit_name != 'No Fit': self.curve_fit.setData( x=self._wm_logger_logic.wlog_fit_x, y=self._wm_logger_logic.wlog_fit_y) if self.curve_fit not in self._mw.plotWidget.listDataItems(): self._mw.plotWidget.addItem(self.curve_fit) else: if self.curve_fit in self._mw.plotWidget.listDataItems(): self._mw.plotWidget.removeItem(self.curve_fit) def add_data_point(self, point): if len(point) >= 3: spts = [{'pos': (point[0], point[1]), 'size': 5, 'brush':pg.intColor(point[2]/100, 255)}] self._scatterplot.addPoints(spts) def stop_resume_clicked(self): """ Handling the Start button to stop and restart the counter. """ # If running, then we stop the measurement and enable inputs again if self._wm_logger_logic.module_state() == 'running': self._mw.actionStop_resume_scan.setText('Resume') self._wm_logger_logic.stop_scanning() self._mw.actionStop_resume_scan.setEnabled(True) self._mw.actionStart_scan.setEnabled(True) self._mw.binSpinBox.setEnabled(True) # Otherwise, we start a measurement and disable some inputs. else: self._mw.actionStop_resume_scan.setText('Stop') self._wm_logger_logic.start_scanning(resume=True) self._mw.actionStart_scan.setEnabled(False) self._mw.binSpinBox.setEnabled(False) def start_clicked(self): """ Handling resume of the scanning without resetting the data. """ if self._wm_logger_logic.module_state() == 'idle': self._scatterplot.clear() self._wm_logger_logic.start_scanning() # Enable the stop button once a scan starts. self._mw.actionStop_resume_scan.setText('Stop') self._mw.actionStop_resume_scan.setEnabled(True) self._mw.actionStart_scan.setEnabled(False) self._mw.binSpinBox.setEnabled(False) self.recalculate_histogram() else: self.log.error('Cannot scan, since a scan is alredy running.') def save_clicked(self): """ Handling the save button to save the data into a file. """ timestamp = datetime.datetime.now() filepath = self._save_logic.get_path_for_module(module_name='WavemeterLogger') filename = os.path.join(filepath, timestamp.strftime('%Y%m%d-%H%M-%S_wavemeter_log_thumbnail')) exporter = pg.exporters.SVGExporter(self._pw.plotItem) exporter.export(filename+'.svg') self._wm_logger_logic.save_data(timestamp=timestamp) def recalculate_histogram(self): self._wm_logger_logic.recalculate_histogram( bins=self._mw.binSpinBox.value(), xmin=self._mw.minDoubleSpinBox.value(), xmax=self._mw.maxDoubleSpinBox.value() ) def set_auto_range(self): self._mw.minDoubleSpinBox.setValue(self._wm_logger_logic.intern_xmin) self._mw.maxDoubleSpinBox.setValue(self._wm_logger_logic.intern_xmax) self.recalculate_histogram() ## Handle view resizing def _update_plot_views(self): ## view has resized; update auxiliary views to match self._right_axis.setGeometry(self._plot_item.vb.sceneBoundingRect()) self._top_axis.setGeometry(self._plot_item.vb.sceneBoundingRect()) ## need to re-update linked axes since this was called ## incorrectly while views had different shapes. ## (probably this should be handled in ViewBox.resizeEvent) self._right_axis.linkedViewChanged(self._plot_item.vb, self._right_axis.XAxis) self._top_axis.linkedViewChanged(self._plot_item.vb, self._top_axis.YAxis)
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() sigClearPlots = QtCore.Signal() sigMWOn = QtCore.Signal() sigMWOff = QtCore.Signal() sigMWPowerChanged = QtCore.Signal(float) sigMWFreqChanged = QtCore.Signal(float) sigFitChanged = QtCore.Signal(str) sigDoFit = QtCore.Signal() 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') self._save_logic = self.get_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) # connect the parameter update events: self._odmr_logic.sigParameterChanged.connect(self.update_parameter) # 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 microwave mode comboWidget self._mw.mode_ComboBox.addItem('Off') self._mw.mode_ComboBox.addItem('CW') ######################################################################## # 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) # 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) self.sigDoFit.connect(self._odmr_logic.do_fit) self.sigFitChanged.connect(self._odmr_logic.fc.set_current_fit) self._odmr_logic.sigOdmrFitUpdated.connect(self.update_fit_display) # 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.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) # Push Buttons self._mw.do_fit_PushButton.clicked.connect(self.do_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): """ Reverse steps of activation @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 """ if not self._mw.action_run_stop.isChecked(): self._mw.action_run_stop.blockSignals(True) self._mw.action_run_stop.setChecked(True) self._mw.action_run_stop.blockSignals(False) self._mw.action_run_stop.setEnabled(True) if not self._mw.action_resume_odmr.isChecked(): self._mw.action_resume_odmr.blockSignals(True) self._mw.action_resume_odmr.setChecked(True) self._mw.action_resume_odmr.blockSignals(False) 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_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( ) 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) def do_fit(self): self.sigFitChanged.emit( self._mw.fit_methods_ComboBox.getCurrentFit()[0]) self.sigDoFit.emit() def update_fit_display(self): """ Do the configured fit and show it in the sum plot """ fit_name = self._odmr_logic.fc.current_fit fit_result = self._odmr_logic.fc.current_fit_result fit_param = self._odmr_logic.fc.current_fit_param if fit_result is not None: # display results as formatted text self._mw.odmr_fit_results_DisplayWidget.clear() try: formated_results = units.create_formatted_output( fit_result.result_str_dict) except: formated_results = 'this fit does not return formatted results' self._mw.odmr_fit_results_DisplayWidget.setPlainText( formated_results) if fit_name is not None: self._mw.fit_methods_ComboBox.setCurrentFit(fit_name) # 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 fit_name != 'No Fit': self.odmr_fit_image.setData(x=self._odmr_logic.ODMR_fit_x, y=self._odmr_logic.ODMR_fit_y) 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() def update_parameter(self, param_dict=None): """ 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 emiting a change back to the logic. """ if param_dict is None: return 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) 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 MagnetControlGui(GUIBase): """ Class for the alignment of the magnetic field. """ _modclass = 'MagnetControlGui' _modtype = 'gui' # declare connectors magnetlogic1 = Connector(interface='MagnetController') savelogic = Connector(interface='SaveLogic') # signals sigFitXChanged = QtCore.Signal(str) sigDoXFit = QtCore.Signal(str) sigFitYChanged = QtCore.Signal(str) sigDoYFit = QtCore.Signal(str) def __init__(self, config, **kwargs): super().__init__(config=config, **kwargs) def on_activate(self): """ Initializes all needed UI files and establishes the connectors. This method executes the all the inits for the differnt GUIs and passes the event argument from fysom to the methods. """ # # Getting an access to all connectors: self._magnet_logic = self.get_connector('magnetlogic1') self._save_logic = self.get_connector('savelogic') self._hardware_state = True self.initMainUI() # initialize the main GUI # Get the image from the logic self.imageX = pg.PlotDataItem(self._magnet_logic.fluor_plot_x, self._magnet_logic.fluor_plot_y, pen=pg.mkPen(palette.c1, style=QtCore.Qt.DotLine), symbol='o', symbolPen=palette.c1, symbolBrush=palette.c1, symbolSize=7) self.imageY = pg.PlotDataItem(self._magnet_logic.yfluor_plot_x, self._magnet_logic.yfluor_plot_y, pen=pg.mkPen(palette.c1, style=QtCore.Qt.DotLine), symbol='o', symbolPen=palette.c1, symbolBrush=palette.c1, symbolSize=7) self.fluor_x_fit_image = pg.PlotDataItem( self._magnet_logic.x_scan_fit_x, self._magnet_logic.x_scan_fit_y, pen=pg.mkPen(palette.c2)) self.fluor_y_fit_image = pg.PlotDataItem( self._magnet_logic.y_scan_fit_x, self._magnet_logic.y_scan_fit_y, pen=pg.mkPen(palette.c2)) # Add the display item to the X_scan and Y_scan, which were defined in the UI file. self._mw.X_scan.addItem(self.imageX) self._mw.X_scan.setLabel(axis='left', text='Counts', units='Counts/s') self._mw.X_scan.setLabel(axis='bottom', text='X axis', units='mm') self._mw.X_scan.showGrid(x=True, y=True, alpha=0.8) self._mw.Y_scan.addItem(self.imageY) self._mw.Y_scan.setLabel(axis='left', text='Counts', units='Counts/s') self._mw.Y_scan.setLabel(axis='bottom', text='X axis', units='mm') self._mw.Y_scan.showGrid(x=True, y=True, alpha=0.8) ######################################################################## # Configuration of the various display Widgets # ######################################################################## # Take the default values from logic: self._mw.x_start.setValue(self._magnet_logic.x_start) self._mw.x_end.setValue(self._magnet_logic.x_end) self._mw.step_x.setValue(self._magnet_logic.step_x) self._mw.y_start.setValue(self._magnet_logic.y_start) self._mw.y_end.setValue(self._magnet_logic.y_end) self._mw.step_y.setValue(self._magnet_logic.step_y) self._mw.acq_time.setValue( self._magnet_logic.fluorescence_integration_time) self._mw.N_AF_points.setValue(self._magnet_logic.N_AF_points) self._mw.set_x_pos.setValue(self._magnet_logic.set_x_pos) self._mw.set_y_pos.setValue(self._magnet_logic.set_y_pos) self._mw.set_z_pos.setValue(self._magnet_logic.set_z_pos) # fit settings self._fsd = FitSettingsDialog(self._magnet_logic.fc) self._fsd.sigFitsUpdated.connect( self._mw.fit_methods_ComboBox.setFitFunctions) self._fsd.sigFitsUpdated.connect( self._mw.fit_methods_ComboBox_2.setFitFunctions) self._fsd.applySettings() self._mw.action_FitSettings.triggered.connect(self._fsd.show) ######################################################################## # Connect signals # ######################################################################## # Internal user input changed signals self._mw.x_start.editingFinished.connect(self.change_x_start) self._mw.x_end.editingFinished.connect(self.change_x_end) self._mw.step_x.editingFinished.connect(self.change_step_x) self._mw.y_start.editingFinished.connect(self.change_y_start) self._mw.y_end.editingFinished.connect(self.change_y_end) self._mw.step_y.editingFinished.connect(self.change_step_y) self._mw.acq_time.editingFinished.connect(self.change_acq_time) self._mw.N_AF_points.editingFinished.connect(self.change_N_AF_points) self._mw.set_x_pos.editingFinished.connect(self.change_set_x_pos) self._mw.set_y_pos.editingFinished.connect(self.change_set_y_pos) self._mw.set_z_pos.editingFinished.connect(self.change_set_z_pos) self._mw.do_x_fit_PushButton.clicked.connect(self.do_x_fit) self.sigDoXFit.connect(self._magnet_logic.do_x_fit, QtCore.Qt.QueuedConnection) self._magnet_logic.sigFitXUpdated.connect(self.update_x_fit, QtCore.Qt.QueuedConnection) self._mw.do_y_fit_PushButton.clicked.connect(self.do_y_fit) self.sigDoYFit.connect(self._magnet_logic.do_y_fit, QtCore.Qt.QueuedConnection) self._magnet_logic.sigFitYUpdated.connect(self.update_y_fit, QtCore.Qt.QueuedConnection) ################################################################# # Actions # ################################################################# # Connect the scan actions to the events if they are clicked. Connect # also the adjustment of the displayed windows. self._mw.action_stop_scanning.triggered.connect(self.ready_clicked) self._scan_x_start_proxy = pg.SignalProxy( self._mw.action_scan_x_start.triggered, delay=0.1, slot=self.x_scan_clicked) self._scan_y_start_proxy = pg.SignalProxy( self._mw.action_scan_y_start.triggered, delay=0.1, slot=self.y_scan_clicked) self._magnet_logic.sigPlotXUpdated.connect(self.update_x_plot, QtCore.Qt.QueuedConnection) self._magnet_logic.sigPlotYUpdated.connect(self.update_y_plot, QtCore.Qt.QueuedConnection) self._magnet_logic.sigPositionUpdated.connect( self.get_current_position) self._magnet_logic.signal_stop_scanning.connect(self.ready_clicked) self._mw.currposbutton.clicked.connect(self.get_current_position) self._mw.appl_pos_butt.clicked.connect(self.apply_position) self._mw.Set_pos_button.clicked.connect(self.set_position) self.get_current_position() return def initMainUI(self): """ Definition, configuration and initialisation of the confocal 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. """ self._mw = MagnetControlMainWindow() def on_deactivate(self): """ Reverse steps of activation @return int: error code (0:OK, -1:error) """ self._mw.close() return 0 def show(self): """Make main window visible and put it above all other windows. """ # Show the Main Magnet Control GUI: self._mw.show() self._mw.activateWindow() self._mw.raise_() ############################################################################ # Change Methods # ############################################################################ def change_x_start(self): """ Update the starting position along x axis in the logic according to the GUI. """ self._magnet_logic.x_start = self._mw.x_start.value() return def change_x_end(self): """ Update the final position along x axis in the logic according to the GUI. """ self._magnet_logic.x_end = self._mw.x_end.value() return def change_step_x(self): """ Update step along x axis in the logic according to the GUI. """ self._magnet_logic.step_x = self._mw.step_x.value() if self._magnet_logic.x_start != 0 and self._magnet_logic.x_end != 0: self._mw.n_x_points.setValue( len( np.arange(self._mw.x_start.value(), self._mw.x_end.value(), self._mw.step_x.value()))) self._magnet_logic.n_x_points = self._mw.n_x_points.value() return def change_y_start(self): """ Update the xy resolution in the logic according to the GUI. """ self._magnet_logic.y_start = self._mw.y_start.value() return def change_y_end(self): """ Update the xy resolution in the logic according to the GUI. """ self._magnet_logic.y_end = self._mw.y_end.value() return def change_step_y(self): """ Update the xy resolution in the logic according to the GUI. """ self._magnet_logic.step_y = self._mw.step_y.value() if self._magnet_logic.y_start != 0 and self._magnet_logic.y_end != 0: self._mw.n_y_points.setValue( len( np.arange(self._mw.y_start.value(), self._mw.y_end.value(), self._mw.step_y.value()))) self._magnet_logic.n_y_points = self._mw.n_y_points.value() return def change_acq_time(self): """ Update the xy resolution in the logic according to the GUI. """ self._magnet_logic.fluorescence_integration_time = self._mw.acq_time.value( ) return def change_N_AF_points(self): """ Update the xy resolution in the logic according to the GUI. """ self._magnet_logic.N_AF_points = self._mw.N_AF_points.value() return def change_set_x_pos(self): """ Update the starting position along x axis in the logic according to the GUI. """ self._magnet_logic.set_x_pos = self._mw.set_x_pos.value() return def change_set_y_pos(self): """ Update the final position along x axis in the logic according to the GUI. """ self._magnet_logic.set_y_pos = self._mw.set_y_pos.value() return def change_set_z_pos(self): """ Update step along x axis in the logic according to the GUI. """ self._magnet_logic.set_z_pos = self._mw.set_z_pos.value() return def get_current_position(self): """ Update current actuators position in the logic according to the GUI. """ self._magnet_logic.get_current_position() time.sleep(0.1) self._mw.curr_x_pos.setValue(self._magnet_logic.curr_x_pos) self._mw.curr_y_pos.setValue(self._magnet_logic.curr_y_pos) self._mw.curr_z_pos.setValue(self._magnet_logic.curr_z_pos) return def apply_position(self): """ Update current actuators position in the logic according to the GUI. """ self._mw.set_x_pos.setValue(self._mw.curr_x_pos.value()) self._mw.set_y_pos.setValue(self._mw.curr_y_pos.value()) self._mw.set_z_pos.setValue(self._mw.curr_z_pos.value()) self._magnet_logic.set_x_pos = self._mw.set_x_pos.value() self._magnet_logic.set_y_pos = self._mw.set_y_pos.value() self._magnet_logic.set_z_pos = self._mw.set_z_pos.value() return def set_position(self): self._magnet_logic.set_position() return def x_scan_clicked(self): """ Manages what happens if the xy scan is started. """ self.disable_scan_actions() self._magnet_logic.start_x_scanning(tag='gui') def y_scan_clicked(self): """ Manages what happens if the xy scan is started. """ self.disable_scan_actions() self._magnet_logic.start_y_scanning(tag='gui') def disable_scan_actions(self): """ Disables the buttons for scanning. """ # Ensable the stop scanning button self._mw.action_stop_scanning.setEnabled(True) # Disable the start scan buttons self._mw.action_scan_x_start.setEnabled(False) self._mw.action_scan_y_start.setEnabled(False) self._mw.set_x_pos.setEnabled(False) self._mw.set_y_pos.setEnabled(False) self._mw.set_z_pos.setEnabled(False) self._mw.acq_time.setEnabled(False) self._mw.x_start.setEnabled(False) self._mw.x_end.setEnabled(False) self._mw.step_x.setEnabled(False) self._mw.y_start.setEnabled(False) self._mw.y_end.setEnabled(False) self._mw.step_y.setEnabled(False) self._mw.N_AF_points.setEnabled(False) def enable_scan_actions(self): """ Disables the buttons for scanning. """ # Disable the start scan buttons self._mw.action_scan_x_start.setEnabled(True) self._mw.action_scan_y_start.setEnabled(True) self._mw.set_x_pos.setEnabled(True) self._mw.set_y_pos.setEnabled(True) self._mw.set_z_pos.setEnabled(True) self._mw.acq_time.setEnabled(True) self._mw.x_start.setEnabled(True) self._mw.x_end.setEnabled(True) self._mw.step_x.setEnabled(True) self._mw.y_start.setEnabled(True) self._mw.y_end.setEnabled(True) self._mw.step_y.setEnabled(True) self._mw.N_AF_points.setEnabled(True) def ready_clicked(self): """ Stopp the scan if the state has switched to ready. """ if self._magnet_logic.module_state() == 'idle': self._magnet_logic.stopRequested = True self.enable_scan_actions() def update_x_plot(self, data_x, data_y): """ Refresh the plot widgets with new data. """ # Update mean signal plot self.imageX.setData(data_x, data_y) def update_y_plot(self, data_x, data_y): """ Refresh the plot widgets with new data. """ # Update mean signal plot self.imageY.setData(data_x, data_y) def do_x_fit(self): fit_function = self._mw.fit_methods_ComboBox.getCurrentFit()[0] self.sigDoXFit.emit(fit_function) return def do_y_fit(self): fit_function = self._mw.fit_methods_ComboBox.getCurrentFit()[0] self.sigDoYFit.emit(fit_function) return def update_x_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.x_fit_result.clear() try: formated_results = units.create_formatted_output( result_str_dict) except: formated_results = 'this fit does not return formatted results' self._mw.x_fit_result.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.fluor_x_fit_image.setData(x=x_data, y=y_data) if self.fluor_x_fit_image not in self._mw.X_scan.listDataItems(): self._mw.X_scan.addItem(self.fluor_x_fit_image) else: if self.fluor_x_fit_image in self._mw.X_scan.listDataItems(): self._mw.X_scan.removeItem(self.fluor_x_fit_image) self._mw.X_scan.getViewBox().updateAutoRange() return def update_y_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.y_fit_result.clear() try: formated_results = units.create_formatted_output( result_str_dict) except: formated_results = 'this fit does not return formatted results' self._mw.y_fit_result.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.fluor_y_fit_image.setData(x=x_data, y=y_data) if self.fluor_y_fit_image not in self._mw.Y_scan.listDataItems(): self._mw.Y_scan.addItem(self.fluor_y_fit_image) else: if self.fluor_y_fit_image in self._mw.Y_scan.listDataItems(): self._mw.Y_scan.removeItem(self.fluor_y_fit_image) self._mw.Y_scan.getViewBox().updateAutoRange() 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 NoiseSpectrumGui(GUIBase): """ Class for the alignment of the magnetic field. """ _modclass = 'MagnetControlGui' _modtype = 'gui' # declare connectors fitlogic = Connector(interface='FitLogic') savelogic = Connector(interface='SaveLogic') fc = StatusVar('fits', None) def __init__(self, config, **kwargs): super().__init__(config=config, **kwargs) def on_activate(self): """ Initializes all needed UI files and establishes the connectors. This method executes the all the inits for the differnt GUIs and passes the event argument from fysom to the methods. """ # # Getting an access to all connectors: self._save_logic = self.get_connector('savelogic') self._fit_logic = self.get_connector('fitlogic') self.initMainUI() # initialize the main GUI self.ex = App() self.initAppUI() self._mw.actionOpen.triggered.connect(self.open_file) self.time = np.zeros(1) self.counts1 = np.zeros(1) self.error1 = np.zeros(1) self.counts2 = np.zeros(1) self.error2 = np.zeros(1) self._mw.sequence_order.editingFinished.connect( self.redraw_normalized_plot) self._mw.contrast.editingFinished.connect(self.redraw_normalized_plot) self._mw.n_FFT.setMaximum(3.0e+06) self._mw.n_FFT.setValue(2.0e+06) self._mw.calculate_filter_function.clicked.connect( self._filter_function_button_fired) self._mw.calculate_ns.clicked.connect( self._calculate_noise_spectrum_button_fired) self.fit_x = np.array([0, 1]) self.fit_y = np.array([0, 1]) self.fit_image = pg.PlotDataItem(self.fit_x, self.fit_y, pen=pg.mkPen(palette.c3)) # fit settings self._fsd = FitSettingsDialog(self.fc) self._fsd.sigFitsUpdated.connect( self._mw.fit_methods_ComboBox.setFitFunctions) self._fsd.applySettings() self._mw.actionFit_Settings.triggered.connect(self._fsd.show) self._mw.do_x_fit_PushButton.clicked.connect(self.do_x_fit) if 'fits' in self._statusVariables and isinstance( self._statusVariables['fits'], dict): self.fc.load_from_dict(self._statusVariables['fits']) return def initMainUI(self): """ Definition, configuration and initialisation of the confocal 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. """ self._mw = NVdepthMainWindow() def initAppUI(self): self.ex.setWindowTitle(self.ex.title) self.ex.setGeometry(self.ex.left, self.ex.top, self.ex.width, self.ex.height) # self.openFileNamesDialog() # self.saveFileDialog() # def openFileNameDialog(self): options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog fileName, _ = QFileDialog.getOpenFileName( self.ex, "QFileDialog.getOpenFileName()", "", "All Files (*.dat);;Python Files (*.txt)", options=options) if fileName: print(fileName) return str(fileName) def on_deactivate(self): """ Reverse steps of activation @return int: error code (0:OK, -1:error) """ self._mw.close() return 0 def show(self): """Make main window visible and put it above all other windows. """ # Show the Main Magnet Control GUI: self._mw.show() self._mw.activateWindow() self._mw.raise_() def open_file(self): self.myfile = self.openFileNameDialog() self.ex.show() self.ex.exec_() self.show_data() def show_data(self): f = open(self.myfile, 'r') lines = f.readlines() result = [] for x in lines: result.append(x.split('#')[0]) f.close() a = [x for x in result if x != ''] self.time = np.zeros(len(a)) self.counts1 = np.zeros(len(a)) self.error1 = np.zeros(len(a)) self.counts2 = np.zeros(len(a)) self.error2 = np.zeros(len(a)) self._mw.data_plot.clear() self._mw.processeddataplot.clear() for i in range(len(a)): self.time[i] = np.asarray(a[i].split(), dtype=np.float32)[0] self.counts1[i] = np.asarray(a[i].split(), dtype=np.float32)[1] self.error1[i] = np.asarray(a[i].split(), dtype=np.float32)[3] self.counts2[i] = np.asarray(a[i].split(), dtype=np.float32)[2] self.error2[i] = np.asarray(a[i].split(), dtype=np.float32)[4] self.data_image1 = pg.PlotDataItem(self.time, self.counts1, pen=pg.mkPen( palette.c1, style=QtCore.Qt.DotLine), symbol='o', symbolPen=palette.c1, symbolBrush=palette.c1, symbolSize=7) self._mw.data_plot.addItem(self.data_image1) self.data_image2 = pg.PlotDataItem(self.time, self.counts2, pen=pg.mkPen( palette.c3, style=QtCore.Qt.DotLine), symbol='o', symbolPen=palette.c3, symbolBrush=palette.c3, symbolSize=7) self._mw.data_plot.addItem(self.data_image2) self._mw.data_plot.setLabel(axis='left', text='Counts', units='Counts/s') self._mw.data_plot.setLabel(axis='bottom', text='time', units='s') self._mw.data_plot.showGrid(x=True, y=True, alpha=0.8) self.baseline = np.sum(self.counts2 + self.counts1) / len( self.counts2) / 2 C0_up = self.baseline / (1 - 0.01 * self._mw.contrast.value() / 2) C0_down = C0_up * (1 - 0.01 * self._mw.contrast.value()) counts = self.counts2 - self.counts1 self.T = self.time * 8 * self._mw.sequence_order.value() self.normalized_counts = (counts) / (C0_up - C0_down) self.normalized_image = pg.PlotDataItem( self.time, #self.T, self.normalized_counts, pen=pg.mkPen(palette.c2, style=QtCore.Qt.DotLine), symbol='o', symbolPen=palette.c2, symbolBrush=palette.c2, symbolSize=7) self._mw.processeddataplot.addItem(self.normalized_image) self._mw.processeddataplot.setLabel(axis='left', text='Counts', units='Counts/s') self._mw.processeddataplot.setLabel(axis='bottom', text='time', units='s') self._mw.processeddataplot.showGrid(x=True, y=True, alpha=0.8) def _calculate_noise_spectrum_button_fired(self): self._mw.spin_noise_plot.clear() S = -np.log(self.normalized_counts) / self.T # self.S = np.concatenate((self.S, S), axis=0) frequency = 1e+6 * 1e-9 * 0.5e+3 / self.time # (in Hz) # self.frequency = np.concatenate((self.frequency, frequency), axis=0) self.noise_spectrum_image = pg.PlotDataItem( frequency * 1e-6, S, pen=pg.mkPen(palette.c5, style=QtCore.Qt.DotLine), symbol='o', symbolPen=palette.c5, symbolBrush=palette.c5, symbolSize=7) self._mw.spin_noise_plot.addItem(self.noise_spectrum_image) self._mw.spin_noise_plot.setLabel(axis='left', text='Intensity', units='arb.u.') self._mw.spin_noise_plot.setLabel(axis='bottom', text='Frequency', units='MHz') self._mw.spin_noise_plot.showGrid(x=True, y=True, alpha=0.8) def redraw_normalized_plot(self): self._mw.processeddataplot.clear() self.baseline = np.sum(self.counts2 + self.counts1) / len( self.counts2) / 2 C0_up = self.baseline / (1 - 0.01 * self._mw.contrast.value() / 2) C0_down = C0_up * (1 - 0.01 * self._mw.contrast.value()) counts = self.counts2 - self.counts1 self.T = self.time * 8 * self._mw.sequence_order.value() self.normalized_counts = (counts) / (C0_up - C0_down) self.normalized_image = pg.PlotDataItem( self.time, #self.T * 1.0e-6, self.normalized_counts, pen=pg.mkPen(palette.c2, style=QtCore.Qt.DotLine), symbol='o', symbolPen=palette.c2, symbolBrush=palette.c2, symbolSize=7) self._mw.processeddataplot.addItem(self.normalized_image) self._mw.processeddataplot.setLabel(axis='left', text='Counts', units='Counts/s') self._mw.processeddataplot.setLabel(axis='bottom', text='time', units='us') self._mw.processeddataplot.showGrid(x=True, y=True, alpha=0.8) return def _filter_function(self, tau): # generate filter function dt = 1e-9 n = int(tau / dt) v = np.zeros(8 * self._mw.sequence_order.value() * n) T = np.linspace(0, dt * n * 8 * self._mw.sequence_order.value(), num=8 * self._mw.sequence_order.value() * n) v[:n // 2] = 1 k = n / 2 + 1 for j in range(8 * self._mw.sequence_order.value() - 1): v[(n // 2 + j * n):(n // 2 + j * n + n)] = (-1)**(j + 1) k = k + 1 v[8 * self._mw.sequence_order.value() * n - n // 2:8 * self._mw.sequence_order.value() * n] = np.ones((n // 2, ), dtype=np.int) return T, v def _fourier_transform(self, tau): T, v = self._filter_function(tau) g = int(self._mw.n_FFT.value()) signalFFT = np.fft.fft(v, g) yf = (np.abs(signalFFT)** 2) * (1e-9) / (8 * self._mw.sequence_order.value()) xf = np.fft.fftfreq(g, 1e-9) self.FFy = yf[0:g] # take only real part self.FFx = xf[0:g] f1 = (1 / (2 * self.time[0])) * 1.1 # bigger f2 = (1 / (2 * self.time[-1])) * 0.5 # smaller yf1 = self.FFy[np.where(self.FFx <= f1)] xf1 = self.FFx[np.where(self.FFx <= f1)] self.FFy = self.FFy[np.where(xf1 >= f2)] self.FFx = self.FFx[np.where(xf1 >= f2)] return def _filter_function_button_fired(self): self._fourier_transform(self.time[self._mw.N_tau.value()]) self._mw.filter_function.clear() self.filter_function_image = pg.PlotDataItem( self.FFx * 1e-6, self.FFy, pen=pg.mkPen(palette.c4, style=QtCore.Qt.DotLine), symbol='o', symbolPen=palette.c4, symbolBrush=palette.c4, symbolSize=7) self._mw.filter_function.addItem(self.filter_function_image) self._mw.filter_function.setLabel(axis='left', text='Intensity', units='arb.u.') self._mw.filter_function.setLabel(axis='bottom', text='Frequency', units='MHz') self._mw.filter_function.showGrid(x=True, y=True, alpha=0.8) return @fc.constructor def sv_set_fits(self, val): # Setup fit container fc = self.fitlogic().make_fit_container('processed', '1d') fc.set_units(['s', 'c/s']) if isinstance(val, dict) and len(val) > 0: fc.load_from_dict(val) else: d1 = OrderedDict() d1['Gaussian peak'] = { 'fit_function': 'gaussian', 'estimator': 'peak' } d1['Lorentzian peak'] = { 'fit_function': 'lorentzian', 'estimator': 'peak' } d1['Two Lorentzian dips'] = { 'fit_function': 'lorentziandouble', 'estimator': 'dip' } d1['N14'] = { 'fit_function': 'lorentziantriple', 'estimator': 'N14' } d1['N15'] = { 'fit_function': 'lorentziandouble', 'estimator': 'N15' } default_fits = OrderedDict() default_fits['1d'] = d1['Lorentzian peak'] fc.load_from_dict(default_fits) return fc @fc.representer def sv_get_fits(self, val): """ save configured fits """ if len(val.fit_list) > 0: return val.save_to_dict() else: return None def get_fit_x_functions(self): """ Return the hardware constraints/limits @return list(str): list of fit function names """ return list(self.fc.fit_list) def do_x_fit(self, fit_function=None, x_data=None, y_data=None): """ Execute the currently configured fit on the measurement data. Optionally on passed data """ fit_function = self.get_fit_x_functions()[0] if (x_data is None) or (y_data is None): x_data = self.time y_data = self.normalized_counts if fit_function is not None and isinstance(fit_function, str): if fit_function in self.get_fit_x_functions(): self.fc.set_current_fit(fit_function) else: self.fc.set_current_fit('No Fit') if fit_function != 'No Fit': self.log.warning( 'Fit function "{0}" not available in ODMRLogic fit container.' ''.format(fit_function)) self.fit_x, self.fit_y, result = self.fc.do_fit(x_data, y_data) print(result) if result is None: result_str_dict = {} else: result_str_dict = result.result_str_dict self.update_x_fit(self.fit_x, self.fit_y, result_str_dict, self.fc.current_fit) return def update_x_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.x_fit_result.clear() try: formated_results = units.create_formatted_output( result_str_dict) except: formated_results = 'this fit does not return formatted results' self._mw.x_fit_result.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.fit_image.setData(x=x_data, y=y_data) if self.fit_image not in self._mw.processeddataplot.listDataItems( ): self._mw.processeddataplot.addItem(self.fit_image) else: if self.fit_image in self._mw.processeddataplot.listDataItems(): self._mw.processeddataplot.removeItem(self.fit_image) # self._mw.X_scan.getViewBox().updateAutoRange() return