diff --git a/src/qudi/gui/powermeter/powermeter_main_window.py b/src/qudi/gui/powermeter/powermeter_main_window.py index 10345d7..2e1bae7 100644 --- a/src/qudi/gui/powermeter/powermeter_main_window.py +++ b/src/qudi/gui/powermeter/powermeter_main_window.py @@ -95,7 +95,7 @@ def resizeEvent(self, event): Adjust the divisor (here, 10) to fine-tune scaling. """ available_width = self.centralWidget().width() - new_font_size = max(12, int(available_width / 10)) + new_font_size = max(12, int(available_width / 8)) font = self.power_label.font() font.setPointSize(new_font_size) self.power_label.setFont(font) diff --git a/src/qudi/gui/terascan/terascan_gui.py b/src/qudi/gui/terascan/terascan_gui.py index c938253..43898e5 100644 --- a/src/qudi/gui/terascan/terascan_gui.py +++ b/src/qudi/gui/terascan/terascan_gui.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- - __all__ = ['TerascanGui'] import numpy as np @@ -17,18 +16,16 @@ from qudi.logic.terascan_logic import TerascanData - -# qudi GUI measurement modules must inherit qudi.core.module.GuiBase or other GUI modules. class TerascanGui(GuiBase): """ Terascan Measurement GUI - + example config for copy-paste: terascan_gui: module.Class: 'terascan.terascan_gui.TerascanGui' connect: terascan_logic: terascan_logic """ - # Signal declaration for outgoing control signals to logic + # Signals for outgoing control signals to logic sigStartMeasurement = QtCore.Signal() sigStopMeasurement = QtCore.Signal() sigSetWavelengths = QtCore.Signal(float, float) @@ -36,20 +33,18 @@ class TerascanGui(GuiBase): sigSetScanRate = QtCore.Signal(int) sigSaveData = QtCore.Signal() - # One logic module + # Connector to the logic module _terascan_logic = Connector(name='terascan_logic', interface='TerascanLogic') - # Declare static parameters that can/must be declared in the qudi configuration - # _my_config_option = ConfigOption(name='my_config_option', default=1, missing='warn') - - # Declare status variables that are saved in the AppStatus upon deactivation of the module and - # are initialized to the saved value again upon activation. + # Status variables saved in the AppStatus: _start_wavelength = StatusVar(name='start_wavelength', default=0.775) _stop_wavelength = StatusVar(name='stop_wavelength', default=0.790) - _current_wavelength = StatusVar('current_wavelength', default=0.75) + _current_wavelength = StatusVar('current_wavelength', default=0.785) + # New status variable for the running average window size: + _running_avg_points = StatusVar(name='running_avg_points', default=5) def on_activate(self) -> None: - # initialize the main window + # Initialize the main window and set wavelength controls: self._mw = TerascanMainWindow() self._mw.start_wavelength.setValue(self._start_wavelength) self._mw.stop_wavelength.setValue(self._stop_wavelength) @@ -59,7 +54,7 @@ def on_activate(self) -> None: for txt, scan_rate in self._terascan_logic().scan_rates.items(): self._mw.scan_rate.addItem(txt, scan_rate) - # connect all GUI internal signals + # Connect GUI internal signals self._mw.start_wavelength.valueChanged.connect(self._start_changed) self._mw.stop_wavelength.valueChanged.connect(self._stop_changed) self._mw.start_stop_button.clicked.connect(self._start_stop_pressed) @@ -67,48 +62,40 @@ def on_activate(self) -> None: self._mw.scan_type.currentIndexChanged.connect(self._scan_type_changed) self._mw.scan_rate.currentIndexChanged.connect(self._scan_rate_changed) - # Connect all signals to and from the logic. Make sure the connections are QueuedConnection. - # Inputs: + # Connect signals from the logic module self._terascan_logic().sigWavelengthUpdated.connect( self._wavelength_changed, QtCore.Qt.QueuedConnection ) - self._terascan_logic().sigCountsUpdated.connect( self._receive_data, QtCore.Qt.QueuedConnection ) - self._terascan_logic().sigScanFinished.connect( self._scan_finished, QtCore.Qt.QueuedConnection - ) - + ) self._terascan_logic().sigLaserLocked.connect( self._laser_lock_ui, QtCore.Qt.QueuedConnection ) - - # Outputs: + # Connect output signals to logic self.sigStartMeasurement.connect( self._terascan_logic().start_scan, QtCore.Qt.QueuedConnection ) self.sigStopMeasurement.connect( self._terascan_logic().stop_scan, QtCore.Qt.QueuedConnection ) - self.sigSetWavelengths.connect( self._terascan_logic().configure_scan, QtCore.Qt.QueuedConnection ) - self.sigSetScanType.connect( - self._terascan_logic().set_scan_type, QtCore.Qt.DirectConnection # this is direct on purpose + self._terascan_logic().set_scan_type, QtCore.Qt.DirectConnection ) - self.sigSetScanRate.connect( self._terascan_logic().set_scan_rate, QtCore.Qt.QueuedConnection ) self._data = [] - # Turn on update timer: + # Set up update timer for plot updates self.__timer = QtCore.QTimer() self.__timer.setSingleShot(False) self.__timer.timeout.connect(self._update_plot) @@ -116,17 +103,19 @@ def on_activate(self) -> None: # Restore saved wavelengths: self.sigSetWavelengths.emit(self._start_wavelength, self._stop_wavelength) - # Show the main window and raise it above all others + # Restore running average points from the StatusVar: + self._mw.spin_avg_points.setValue(self._running_avg_points) + # Connect changes of the spin box to update our status variable + self._mw.spin_avg_points.valueChanged.connect(self._update_running_avg_points) + self.show() def on_deactivate(self) -> None: - # Disconnect all connections done in "on_activate" that are inputs self._terascan_logic().sigWavelengthUpdated.disconnect(self._wavelength_changed) self._terascan_logic().sigCountsUpdated.disconnect(self._receive_data) self._terascan_logic().sigScanFinished.disconnect(self._scan_finished) self._terascan_logic().sigLaserLocked.disconnect(self._laser_lock_ui) - # Use "plain" disconnects (without argument) only on signals owned by this module self._mw.start_wavelength.valueChanged.disconnect() self._mw.stop_wavelength.valueChanged.disconnect() self._mw.start_stop_button.clicked.disconnect() @@ -134,71 +123,52 @@ def on_deactivate(self) -> None: self.sigStopMeasurement.disconnect() self.sigSetWavelengths.disconnect() - # disable update timer: self.__timer.stop() self.__timer.timeout.disconnect() self.__timer = None - - # Close main window + + self._mw.spin_avg_points.valueChanged.disconnect(self._update_running_avg_points) self._mw.close() - def show(self) -> None: - """ Mandatory method to show the main window """ self._mw.show() self._mw.raise_() - - ### Handlers from the UI ### + # Handlers from the UI: @QtCore.Slot(float) def _start_changed(self, wave: float) -> None: - """ Qt slot to be called upon wavelength change """ self._start_wavelength = wave self.sigSetWavelengths.emit(self._start_wavelength, self._stop_wavelength) - @QtCore.Slot(float) def _stop_changed(self, wave: float) -> None: - """ Qt slot to be called upon wavelength change """ self._stop_wavelength = wave self.sigSetWavelengths.emit(self._start_wavelength, self._stop_wavelength) - @QtCore.Slot() def _start_stop_pressed(self) -> None: - """ Qt slot to be called upon wavelength change """ if self._mw.start_stop_button.text() == 'Start Measurement': self._update_ui(True) - self.__timer.start(250) + self.__timer.start(250) self.sigStartMeasurement.emit() else: self._update_ui(False) self.__timer.stop() self.sigStopMeasurement.emit() - @QtCore.Slot(int) def _scan_type_changed(self, _: int): - """ Qt slot to be called upon scan type change """ - self.sigSetScanType.emit(self._mw.scan_type.currentData().value) - # When type changes, update UI for allowed rates - self._mw.scan_rate.clear() # Clear previous rates + self._mw.scan_rate.clear() for txt, scan_rate in self._terascan_logic().scan_rates.items(): self._mw.scan_rate.addItem(txt, scan_rate) - @QtCore.Slot(int) def _scan_rate_changed(self, _: int): - """ Qt slot to be called upon scan rate change """ if self._mw.scan_rate.currentData() is not None: self.sigSetScanRate.emit(self._mw.scan_rate.currentData().value) - ### End Handlers from UI ### - - - ### Begin Handlers from Logic ### - + # Handlers from the Logic: @QtCore.Slot() def _scan_finished(self) -> None: self._mw.start_stop_button.setText('Start Measurement') @@ -207,58 +177,62 @@ def _scan_finished(self) -> None: def _receive_data(self, data: List[TerascanData]) -> None: self._data = data - @QtCore.Slot(float) def _wavelength_changed(self, wave: float) -> None: self._current_wavelength = wave - percent = 100 * (wave*1e-3 - self._start_wavelength) / (self._stop_wavelength - self._start_wavelength); + percent = 100 * (wave*1e-3 - self._start_wavelength) / (self._stop_wavelength - self._start_wavelength) self._mw._progress_bar.setValue(int(round(percent))) @QtCore.Slot() def _save_data(self) -> None: ds = TextDataStorage( - root_dir=self.module_default_data_dir, - column_formats='.15e' - ) - + root_dir=self.module_default_data_dir, + column_formats='.15e' + ) array = np.array([(d.wavelength, d.counts) for d in self._data]) ds.save_data(array) - + @QtCore.Slot(bool) def _laser_lock_ui(self, locked: bool) -> None: icon = 'network-connect' if locked else 'network-disconnect' pix = QtGui.QPixmap(os.path.join(get_artwork_dir(), 'icons', icon)) self._mw._locked_indicator.setPixmap(pix.scaled(16, 16)) - - ### End Handlers from Logic ### - - - ### Begin Private internal Functions ### + # Private internal functions: def _update_ui(self, running: bool) -> None: if running: self._mw.start_stop_button.setText('Stop Measurement') self._mw.plot_widget.setXRange(self._start_wavelength, self._stop_wavelength) self._mw._statusbar.clearMessage() self._mw._progress_bar.setValue(0) - else: self._mw.start_stop_button.setText('Start Measurement') self._mw._statusbar.showMessage('Ready') - # TODO: Make this more efficient. There is no reason to redraw the entire plot each time. - def _update_plot(self) -> None: - if (len(self._data) == 0): + def _update_plot(self): + # Make a local snapshot of the data + local_data = self._data[:] + if not local_data: return - - - # x, y = self._mw.data_item.getData() - # new_count = len(self._data) - len(x) - - img = np.zeros((len(self._data),2)) - for i in range(len(self._data) - 1): - img[i][0] = self._data[i].wavelength*1e-3 - img[i][1] = self._data[i].counts - self._mw.data_item.setData(x = img[:, 0], y = img[:, 1]) + # Convert data to arrays + x_array = np.array([d.wavelength*1e-3 for d in local_data]) + y_array = np.array([d.counts for d in local_data]) + + # If running average is enabled, apply a rolling average + if self._mw.checkbox_running_avg.isChecked(): + window_size = self._mw.spin_avg_points.value() + if window_size > 1 and window_size <= len(y_array): + kernel = np.ones(window_size) / float(window_size) + y_array = np.convolve(y_array, kernel, mode='same') + # Final sanity-check + if x_array.shape[0] != y_array.shape[0]: + return # skip this update + + self._mw.data_item.setData(x=x_array, y=y_array) + + + @QtCore.Slot(int) + def _update_running_avg_points(self, points: int) -> None: + self._running_avg_points = points diff --git a/src/qudi/gui/terascan/terascan_main_window.py b/src/qudi/gui/terascan/terascan_main_window.py index 43360f6..9e3ac74 100644 --- a/src/qudi/gui/terascan/terascan_main_window.py +++ b/src/qudi/gui/terascan/terascan_main_window.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- - __all__ = ['TerascanMainWindow'] import os @@ -13,14 +12,11 @@ from qudi.hardware.laser.solstis_constants import * class TerascanMainWindow(QtWidgets.QMainWindow): - """ Main window for Terascan measurement - """ + """ Main window for Terascan measurement """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - # Construct a simple GUI window with some widgets to tinker with self.setWindowTitle('Terascan Measurement') - # Create menu bar menu_bar = QtWidgets.QMenuBar() menu = menu_bar.addMenu('File') @@ -39,22 +35,18 @@ def __init__(self, *args, **kwargs): # Create statusbar and indicators self._statusbar = self.statusBar() - # self._statusbar.showMessage('Ready') - self._progress_bar = QtWidgets.QProgressBar() self._progress_bar.setRange(0, 100) self._progress_bar.setValue(0) - self._locked_indicator = QtWidgets.QLabel() - self._locked_indicator.setPixmap(QtGui.QPixmap(os.path.join(get_artwork_dir(), 'icons', 'network-disconnect')).scaled(16, 16)) - + self._locked_indicator.setPixmap( + QtGui.QPixmap(os.path.join(get_artwork_dir(), 'icons', 'network-disconnect')).scaled(16, 16) + ) self._statusbar.addWidget(self._locked_indicator) self._statusbar.addWidget(self._progress_bar) - # Initialize widgets - self.start_stop_button = QtWidgets.QPushButton('Start Measurement') - + # Initialize widgets for wavelengths, scan, etc. self.start_wavelength_label = QtWidgets.QLabel('Start Wavelength (um)') self.start_wavelength = QtWidgets.QDoubleSpinBox() self.start_wavelength.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons) @@ -78,38 +70,57 @@ def __init__(self, *args, **kwargs): self.plot_widget = pg.PlotWidget() self.plot_widget.getPlotItem().setContentsMargins(1, 1, 1, 1) self.plot_widget.setSizePolicy(QtWidgets.QSizePolicy.Expanding, - QtWidgets.QSizePolicy.Expanding) + QtWidgets.QSizePolicy.Expanding) self.plot_widget.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) self.plot_widget.setLabel('bottom', text='Wavelength', units='um') self.plot_widget.setLabel('left', text='Counts') - self.data_item = pg.PlotDataItem(pen=pg.mkPen(palette.c1, style=QtCore.Qt.DotLine), - symbol='o', - symbolPen=palette.c1, - symbolBrush=palette.c1, - symbolSize=7) + self.data_item = pg.PlotDataItem( + pen=pg.mkPen(palette.c1, style=QtCore.Qt.DotLine), + symbol='o', + symbolPen=palette.c1, + symbolBrush=palette.c1, + symbolSize=7 + ) self.plot_widget.addItem(self.data_item) - # arrange widgets in layout + # New Running Average controls + self.checkbox_running_avg = QtWidgets.QCheckBox("Enable Running Average") + self.checkbox_running_avg.setChecked(False) + self.label_avg_points = QtWidgets.QLabel("Points in Rolling Average:") + self.spin_avg_points = QtWidgets.QSpinBox() + self.spin_avg_points.setRange(1, 9999) + self.spin_avg_points.setValue(5) # default value; this will be set from a StatusVar in the GUI + + # The Start Measurement button (we want this at the very bottom) + self.start_stop_button = QtWidgets.QPushButton('Start Measurement') + + # Arrange widgets in layout layout = QtWidgets.QGridLayout() layout.addWidget(self.plot_widget, 0, 0, 4, 4) - control_layout = QtWidgets.QVBoxLayout() + control_layout = QtWidgets.QVBoxLayout() control_layout.addWidget(self.scan_type_label, 0, QtCore.Qt.AlignBottom) control_layout.addWidget(self.scan_type, 0, QtCore.Qt.AlignTop) control_layout.addWidget(self.scan_rate_label, 0, QtCore.Qt.AlignBottom) - control_layout.addWidget(self.scan_rate, 0, QtCore.Qt.AlignTop) + control_layout.addWidget(self.scan_rate, 0, QtCore.Qt.AlignTop) control_layout.addWidget(self.start_wavelength_label, 0, QtCore.Qt.AlignBottom) control_layout.addWidget(self.start_wavelength, 0, QtCore.Qt.AlignTop) control_layout.addWidget(self.stop_wavelength_label, 0, QtCore.Qt.AlignBottom) control_layout.addWidget(self.stop_wavelength, 0, QtCore.Qt.AlignTop) + + # Place Running Average controls ABOVE the Start Measurement button: + control_layout.addWidget(self.checkbox_running_avg) + control_layout.addWidget(self.label_avg_points) + control_layout.addWidget(self.spin_avg_points) + + # Add stretch to push the start button to the bottom: + control_layout.addStretch() control_layout.addWidget(self.start_stop_button) layout.addLayout(control_layout, 0, 5, 5, 1) layout.setColumnStretch(1, 1) - - - # Create dummy widget as main widget and set layout + central_widget = QtWidgets.QWidget() central_widget.setLayout(layout) self.setCentralWidget(central_widget) diff --git a/src/qudi/logic/terascan_logic.py b/src/qudi/logic/terascan_logic.py index 231f659..8984e42 100644 --- a/src/qudi/logic/terascan_logic.py +++ b/src/qudi/logic/terascan_logic.py @@ -62,9 +62,9 @@ class TerascanLogic(LogicBase): _mode_hop_overlap_fine = ConfigOption(name='mode_hop_overlap_fine', default=0.00025) # status variables: - _start_wavelength = StatusVar('start_wavelength', default=0.75) - _end_wavelength = StatusVar('end_wavelength', default=0.8) - _current_wavelength = StatusVar('current_wavelength', default=0.75) + _start_wavelength = StatusVar('start_wavelength', default=0.785) + _end_wavelength = StatusVar('end_wavelength', default=0.7851) + _current_wavelength = StatusVar('current_wavelength', default=0.785) _scan_rate = StatusVar('scan_rate', default=12) # SCAN_RATE_FINE_LINE_20_GHZ _scan_type = StatusVar('scan_type', default=2) # SCAN_TYPE_FINE