diff --git a/src/qudi/gui/terascan/terascan_gui copy.py b/src/qudi/gui/terascan/terascan_gui copy.py new file mode 100644 index 0000000..e912a8c --- /dev/null +++ b/src/qudi/gui/terascan/terascan_gui copy.py @@ -0,0 +1,367 @@ +# -*- coding: utf-8 -*- +__all__ = ['TerascanGui'] + +import numpy as np +import os +from PySide2 import QtCore, QtGui, QtWidgets +import pyqtgraph as pg +from typing import List +from time import sleep + +from qudi.util.datastorage import TextDataStorage +from qudi.core.module import GuiBase +from qudi.core.connector import Connector +from qudi.core.configoption import ConfigOption +from qudi.core.statusvariable import StatusVar +# from qudi.gui.terascan.terascan_main_window import TerascanMainWindow +from qudi.util.paths import get_artwork_dir +from qudi.util.colordefs import QudiPalettePale as palette +from enum import Enum + +# TODO: This is a copy of the one in the logic module. We should probably move this to a common place. +class TeraScanType(Enum): + SCAN_TYPE_MEDIUM = 1 + SCAN_TYPE_FINE = 2 + SCAN_TYPE_LINE = 3 + +class TeraScanRate(Enum): + SCAN_RATE_MEDIUM_100_GHZ = 4 + SCAN_RATE_MEDIUM_50_GHZ = 5 + SCAN_RATE_MEDIUM_20_GHZ = 6 + SCAN_RATE_MEDIUM_15_GHZ = 7 + SCAN_RATE_MEDIUM_10_GHZ = 8 + SCAN_RATE_MEDIUM_5_GHZ = 9 + SCAN_RATE_MEDIUM_2_GHZ = 10 + SCAN_RATE_MEDIUM_1_GHZ = 11 + SCAN_RATE_FINE_LINE_20_GHZ = 12 + SCAN_RATE_FINE_LINE_10_GHZ = 13 + SCAN_RATE_FINE_LINE_5_GHZ = 14 + SCAN_RATE_FINE_LINE_2_GHZ = 15 + SCAN_RATE_FINE_LINE_1_GHZ = 16 + SCAN_RATE_FINE_LINE_500_MHZ = 17 + SCAN_RATE_FINE_LINE_200_MHZ = 18 + SCAN_RATE_FINE_LINE_100_MHZ = 19 + SCAN_RATE_FINE_LINE_50_MHZ = 20 + SCAN_RATE_FINE_LINE_20_MHZ = 21 + SCAN_RATE_FINE_LINE_10_MHZ = 22 + SCAN_RATE_FINE_LINE_5_MHZ = 23 + SCAN_RATE_FINE_LINE_2_MHZ = 24 + SCAN_RATE_FINE_LINE_1_MHZ = 25 + SCAN_RATE_LINE_500_KHZ = 26 + SCAN_RATE_LINE_200_KHZ = 27 + SCAN_RATE_LINE_100_KHZ = 28 + SCAN_RATE_LINE_50_KHZ = 29 + +pg.setConfigOption('useOpenGL', True) # Add this at the top of your file + + +# TODO: No status variables. Just grab the ones from the logic module. +class TerascanGui(GuiBase): + """ Terascan Measurement GUI + + example config for copy-paste: + terascan_gui: + module.Class: 'terascan.terascan_gui.TerascanGui' + connect: + terascan_logic: terascan_logic + """ + + ####################### SIGNALS FROM GUI TO LOGIC #################### + sigStartMeasurement = QtCore.Signal() + sigStopMeasurement = QtCore.Signal() + sigSetStartWavelength = QtCore.Signal(float) + sigSetStopWavelength = QtCore.Signal(float) + sigSetScanType = QtCore.Signal(int) + sigSetScanRate = QtCore.Signal(int) + sigSaveData = QtCore.Signal() + + #################### CONNECTOR TO LOGIC #################### + _terascan_logic = Connector(name='terascan_logic', interface='TerascanLogic') + + #################### STATUS VARIABLES #################### + _running_avg_points = StatusVar(name='running_avg_points', default=5) + + # TODO: operate in terascan_logic instead of terascan_logic() + def on_activate(self) -> None: + # Initialize the main window and set wavelength controls: + self._mw = TerascanMainWindow() + + for txt, scan_type in self._terascan_logic().scan_types.items(): + self._mw.scan_type.addItem(txt, scan_type) + for txt, scan_rate in self._terascan_logic().scan_rates.items(): + self._mw.scan_rate.addItem(txt, scan_rate) + + ################# CONNECT SIGNALS FROM GUI TO GUI ############################ + 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) + self._mw.action_save_data.triggered.connect(self._save_data) + self._mw.scan_type.currentIndexChanged.connect(self._scan_type_changed) + self._mw.scan_rate.currentIndexChanged.connect(self._scan_rate_changed) + + #################### CONNECT SIGNALS FROM LOGIC TO GUI #################### + self._terascan_logic().sigNewData.connect(self._update_data) # Update the GUI with new data + self._terascan_logic().sigSetScanType.connect(self._set_scan_type) # Update the GUI with new scan type + self._terascan_logic().sigSetScanRate.connect(self._set_scan_rate) # Update the GUI with new scan rate + self._terascan_logic().sigScanStarted.connect(self._scan_started) # Update the GUI when the scan starts + self._terascan_logic().sigScanStopped.connect(self._scan_stopped) # Update the GUI when the scan stops + + + ################### CONNECT SIGNALS FROM GUI 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.sigSetStartWavelength.connect( + self._terascan_logic().set_start_wavelength, QtCore.Qt.QueuedConnection + ) + self.sigSetStopWavelength.connect( + self._terascan_logic().set_stop_wavelength, QtCore.Qt.QueuedConnection + ) + self.sigSetScanType.connect( + self._terascan_logic().set_scan_type, QtCore.Qt.DirectConnection + ) + self.sigSetScanRate.connect( + self._terascan_logic().set_scan_rate, QtCore.Qt.QueuedConnection + ) + self.sigSaveData.connect( + self._terascan_logic().save_data, QtCore.Qt.QueuedConnection + ) + + ####################### REFERENCE TO TERASCAN DATA #################### + self.wavelength_data: List[float] = [] # Wavelength data from the logic module + self.counts_data: List[float] = [] # Counts data from the logic module + + # Set up update timer for plot updates + self.__timer = QtCore.QTimer() + self.__timer.setSingleShot(False) + self.__timer.timeout.connect(self.__update_gui) + self.__timer.start(250) # Update every 250 ms + # TODO: Make this more configurable and maybe more efficient? + + # 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: + self._terascan_logic().sigNewData.disconnect(self._update_data) + + self._mw.start_wavelength.valueChanged.disconnect() + self._mw.stop_wavelength.valueChanged.disconnect() + self._mw.start_stop_button.clicked.disconnect() + self.sigStartMeasurement.disconnect() + self.sigStopMeasurement.disconnect() + self.sigSetWavelengths.disconnect() + + self.__timer.stop() + self.__timer.timeout.disconnect() + self.__timer = None + + self._mw.spin_avg_points.valueChanged.disconnect(self._update_running_avg_points) + self._mw.close() + + def show(self) -> None: + self._mw.show() + self._mw.raise_() + + def _save_data(self) -> None: + """ Tell the logic to save the data """ + self.sigSaveData.emit() + + # Handlers from the UI: + @QtCore.Slot(float) + def _start_changed(self, wave: float) -> None: + self.sigSetStartWavelength.emit(wave) + + @QtCore.Slot(float) + def _stop_changed(self, wave: float) -> None: + self.sigSetStopWavelength.emit(wave) + + @QtCore.Slot() + def _start_stop_pressed(self) -> None: + if self._mw.start_stop_button.text() == 'Start Measurement': + self.sigStartMeasurement.emit() # Tell the logic to start the scan + + # TODO: This should be a signal + self._terascan_logic().clear_data() + start_wl = self._terascan_logic().start_wavelength + stop_wl = self._terascan_logic().stop_wavelength + self._mw.plot_widget.setXRange(start_wl, stop_wl) + else: + self.sigStopMeasurement.emit() # Tell the logic to stop the scan + + + + + # @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)) + + # Private internal functions: + def _scan_started(self) -> None: + """ Update the GUI when the scan starts """ + self._mw.start_stop_button.setText('Stop Measurement') + self._mw._statusbar.clearMessage() + self._mw._progress_bar.setValue(0) + + def _scan_stopped(self) -> None: + """ Update the GUI when the scan stops """ + self._mw.start_stop_button.setText('Start Measurement') + self._mw._statusbar.showMessage('Ready') + self._mw._progress_bar.setValue(100) + + @QtCore.Slot() + def __update_gui(self): + x_array = self.wavelength_data + y_array = self.counts_data + + if len(x_array) == 0 or len(y_array) == 0: + return + + # 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') + + + 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 + + @QtCore.Slot() + def _update_data(self, wavelength_data, counts_data) -> None: + """ Update the data from the logic module """ + self.wavelength_data = wavelength_data + self.counts_data = counts_data + + + +class TerascanMainWindow(QtWidgets.QMainWindow): + """ Main window for Terascan measurement """ + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.setWindowTitle('Terascan Measurement') + self.resize(1250, 500) + + # Create menu bar + menu_bar = QtWidgets.QMenuBar() + menu = menu_bar.addMenu('File') + self.action_save_data = QtWidgets.QAction('Save Data') + path = os.path.join(get_artwork_dir(), 'icons', 'document-save') + self.action_save_data.setIcon(QtGui.QIcon(path)) + menu.addAction(self.action_save_data) + menu.addSeparator() + + self.action_close = QtWidgets.QAction('Close') + path = os.path.join(get_artwork_dir(), 'icons', 'application-exit') + self.action_close.setIcon(QtGui.QIcon(path)) + self.action_close.triggered.connect(self.close) + menu.addAction(self.action_close) + self.setMenuBar(menu_bar) + + # Create statusbar and indicators + self._statusbar = self.statusBar() + 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._statusbar.addWidget(self._locked_indicator) + self._statusbar.addWidget(self._progress_bar) + + # 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) + self.start_wavelength.setAlignment(QtCore.Qt.AlignHCenter) + self.start_wavelength.setRange(0.3, 2) + self.start_wavelength.setDecimals(6) + + self.stop_wavelength_label = QtWidgets.QLabel('Stop Wavelength (um)') + self.stop_wavelength = QtWidgets.QDoubleSpinBox() + self.stop_wavelength.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons) + self.stop_wavelength.setAlignment(QtCore.Qt.AlignHCenter) + self.stop_wavelength.setRange(0.3, 2) + self.stop_wavelength.setDecimals(6) + + self.scan_rate_label = QtWidgets.QLabel('Scan Rate') + self.scan_rate = QtWidgets.QComboBox() + + self.scan_type_label = QtWidgets.QLabel('Scan Type') + self.scan_type = QtWidgets.QComboBox() + + self.plot_widget = pg.PlotWidget() + self.plot_widget.setAntialiasing(False) + self.plot_widget.getPlotItem().setContentsMargins(1, 1, 1, 1) + self.plot_widget.setSizePolicy(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.SolidLine), + # downsampling if you want + # downsample=10, # Render 1 out of every 10 points + # downsampleMethod='mean' # Average points for smoother results + ) + self.plot_widget.addItem(self.data_item) + + # 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.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.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) + + central_widget = QtWidgets.QWidget() + central_widget.setLayout(layout) + self.setCentralWidget(central_widget) diff --git a/src/qudi/gui/terascan/terascan_gui.py b/src/qudi/gui/terascan/terascan_gui.py index b63e5dc..caa70ca 100644 --- a/src/qudi/gui/terascan/terascan_gui.py +++ b/src/qudi/gui/terascan/terascan_gui.py @@ -6,32 +6,65 @@ from PySide2 import QtCore, QtGui, QtWidgets import pyqtgraph as pg from typing import List -from time import sleep -from qudi.util.datastorage import TextDataStorage from qudi.core.module import GuiBase from qudi.core.connector import Connector -from qudi.core.configoption import ConfigOption from qudi.core.statusvariable import StatusVar -# from qudi.gui.terascan.terascan_main_window import TerascanMainWindow from qudi.util.paths import get_artwork_dir from qudi.util.colordefs import QudiPalettePale as palette -pg.setConfigOption('useOpenGL', True) # Add this at the top of your file - - -# TODO: No status variables. Just grab the ones from the logic module. +from enum import Enum + + +# ---------------------------------------------------------------------- +# enums (kept local – same numeric values as in the logic) +# ---------------------------------------------------------------------- +class TeraScanType(Enum): + SCAN_TYPE_MEDIUM = 1 + SCAN_TYPE_FINE = 2 + SCAN_TYPE_LINE = 3 + + +class TeraScanRate(Enum): + SCAN_RATE_MEDIUM_100_GHZ = 4 + SCAN_RATE_MEDIUM_50_GHZ = 5 + SCAN_RATE_MEDIUM_20_GHZ = 6 + SCAN_RATE_MEDIUM_15_GHZ = 7 + SCAN_RATE_MEDIUM_10_GHZ = 8 + SCAN_RATE_MEDIUM_5_GHZ = 9 + SCAN_RATE_MEDIUM_2_GHZ = 10 + SCAN_RATE_MEDIUM_1_GHZ = 11 + SCAN_RATE_FINE_LINE_20_GHZ = 12 + SCAN_RATE_FINE_LINE_10_GHZ = 13 + SCAN_RATE_FINE_LINE_5_GHZ = 14 + SCAN_RATE_FINE_LINE_2_GHZ = 15 + SCAN_RATE_FINE_LINE_1_GHZ = 16 + SCAN_RATE_FINE_LINE_500_MHZ = 17 + SCAN_RATE_FINE_LINE_200_MHZ = 18 + SCAN_RATE_FINE_LINE_100_MHZ = 19 + SCAN_RATE_FINE_LINE_50_MHZ = 20 + SCAN_RATE_FINE_LINE_20_MHZ = 21 + SCAN_RATE_FINE_LINE_10_MHZ = 22 + SCAN_RATE_FINE_LINE_5_MHZ = 23 + SCAN_RATE_FINE_LINE_2_MHZ = 24 + SCAN_RATE_FINE_LINE_1_MHZ = 25 + SCAN_RATE_LINE_500_KHZ = 26 + SCAN_RATE_LINE_200_KHZ = 27 + SCAN_RATE_LINE_100_KHZ = 28 + SCAN_RATE_LINE_50_KHZ = 29 + + +pg.setConfigOption('useOpenGL', True) + + +# ────────────────────────────────────────────────────────────────────── class TerascanGui(GuiBase): - """ Terascan Measurement GUI - - example config for copy-paste: - terascan_gui: - module.Class: 'terascan.terascan_gui.TerascanGui' - connect: - terascan_logic: terascan_logic + """ + GUI module for the Terascan logic. unchanged layout – just patched + so the module loads and stays in sync with the logic. """ - ####################### SIGNALS FROM GUI TO LOGIC #################### + # ▸▸ GUI → logic signals sigStartMeasurement = QtCore.Signal() sigStopMeasurement = QtCore.Signal() sigSetStartWavelength = QtCore.Signal(float) @@ -40,23 +73,28 @@ class TerascanGui(GuiBase): sigSetScanRate = QtCore.Signal(int) sigSaveData = QtCore.Signal() - #################### CONNECTOR TO LOGIC #################### + # connector _terascan_logic = Connector(name='terascan_logic', interface='TerascanLogic') - #################### STATUS VARIABLES #################### + # one GUI preference _running_avg_points = StatusVar(name='running_avg_points', default=5) - # TODO: operate in terascan_logic instead of terascan_logic() - def on_activate(self) -> None: - # Initialize the main window and set wavelength controls: + # ─────────────── Qudi life-cycle ─────────────────────────────── + def on_activate(self): + # create the main window self._mw = TerascanMainWindow() + # ← populate start/stop λ in nm from the logic’s µm values + logic = self._terascan_logic() + self._mw.start_wavelength.setValue(logic.get_start_wavelength()) + self._mw.stop_wavelength.setValue(logic.get_stop_wavelength()) + # populate combo-boxes with entries from the logic for txt, scan_type in self._terascan_logic().scan_types.items(): self._mw.scan_type.addItem(txt, scan_type) for txt, scan_rate in self._terascan_logic().scan_rates.items(): self._mw.scan_rate.addItem(txt, scan_rate) - ################# CONNECT SIGNALS FROM GUI TO GUI ############################ + # ▸ GUI widgets → local slots 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) @@ -64,158 +102,147 @@ 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 SIGNALS FROM LOGIC TO GUI #################### - self._terascan_logic().sigNewData.connect(self._update_data) # Update the GUI with new data - self._terascan_logic().sigSetScanType.connect(self._set_scan_type) # Update the GUI with new scan type - self._terascan_logic().sigSetScanRate.connect(self._set_scan_rate) # Update the GUI with new scan rate - self._terascan_logic().sigScanStarted.connect(self._scan_started) # Update the GUI when the scan starts - self._terascan_logic().sigScanStopped.connect(self._scan_stopped) # Update the GUI when the scan stops - - - ################### CONNECT SIGNALS FROM GUI 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.sigSetStartWavelength.connect( - self._terascan_logic().set_start_wavelength, QtCore.Qt.QueuedConnection - ) - self.sigSetStopWavelength.connect( - self._terascan_logic().set_stop_wavelength, QtCore.Qt.QueuedConnection - ) - self.sigSetScanType.connect( - self._terascan_logic().set_scan_type, QtCore.Qt.DirectConnection - ) - self.sigSetScanRate.connect( - self._terascan_logic().set_scan_rate, QtCore.Qt.QueuedConnection - ) - self.sigSaveData.connect( - self._terascan_logic().save_data, QtCore.Qt.QueuedConnection - ) - - ####################### REFERENCE TO TERASCAN DATA #################### - self.wavelength_data: List[float] = [] # Wavelength data from the logic module - self.counts_data: List[float] = [] # Counts data from the logic module - - # Set up update timer for plot updates - self.__timer = QtCore.QTimer() - self.__timer.setSingleShot(False) + # ▸ logic → GUI + logic = self._terascan_logic() + logic.sigNewData.connect(self._update_data) # fixed signature + logic.sigScanTypeChanged.connect(self._set_scan_type) # corrected names + logic.sigScanRateChanged.connect(self._set_scan_rate) + logic.sigScanStarted.connect(self._scan_started) + logic.sigScanStopped.connect(self._scan_stopped) + + # ▸ GUI → logic + self.sigStartMeasurement.connect(logic.start_scan, QtCore.Qt.QueuedConnection) + self.sigStopMeasurement.connect(logic.stop_scan, QtCore.Qt.QueuedConnection) + self.sigSetStartWavelength.connect(logic.set_start_wavelength, QtCore.Qt.QueuedConnection) + self.sigSetStopWavelength.connect(logic.set_stop_wavelength, QtCore.Qt.QueuedConnection) + self.sigSetScanType.connect(logic.set_scan_type, QtCore.Qt.QueuedConnection) + self.sigSetScanRate.connect(logic.set_scan_rate, QtCore.Qt.QueuedConnection) + self.sigSaveData.connect(logic.save_data, QtCore.Qt.QueuedConnection) + + # keep copies of live data + self.wavelength_data: List[float] = [] + self.counts_data: List[float] = [] + + # 250 ms plot refresh + self.__timer = QtCore.QTimer(self) self.__timer.timeout.connect(self.__update_gui) - self.__timer.start(250) # Update every 250 ms - # TODO: Make this more configurable and maybe more efficient? + self.__timer.start(250) - # Restore running average points from the StatusVar: + # restore running-avg preference 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: - self._terascan_logic().sigNewData.disconnect(self._update_data) - - self._mw.start_wavelength.valueChanged.disconnect() - self._mw.stop_wavelength.valueChanged.disconnect() - self._mw.start_stop_button.clicked.disconnect() - self.sigStartMeasurement.disconnect() - self.sigStopMeasurement.disconnect() - self.sigSetWavelengths.disconnect() + def on_deactivate(self): + logic = self._terascan_logic() + logic.sigNewData.disconnect(self._update_data) + logic.sigScanTypeChanged.disconnect(self._set_scan_type) + logic.sigScanRateChanged.disconnect(self._set_scan_rate) self.__timer.stop() self.__timer.timeout.disconnect() - self.__timer = None - self._mw.spin_avg_points.valueChanged.disconnect(self._update_running_avg_points) self._mw.close() - def show(self) -> None: - self._mw.show() - self._mw.raise_() - - def _save_data(self) -> None: - """ Tell the logic to save the data """ - self.sigSaveData.emit() - - # Handlers from the UI: + # ─────────── GUI → logic handler slots ─────────────────────────── @QtCore.Slot(float) - def _start_changed(self, wave: float) -> None: - self.sigSetStartWavelength.emit(wave) + def _start_changed(self, wl): + self.sigSetStartWavelength.emit(wl) @QtCore.Slot(float) - def _stop_changed(self, wave: float) -> None: - self.sigSetStopWavelength.emit(wave) + def _stop_changed(self, wl): + self.sigSetStopWavelength.emit(wl) + + @QtCore.Slot(int) + def _scan_type_changed(self, index): + val = self._mw.scan_type.itemData(index) + if val is not None: + self.sigSetScanType.emit(val.value) + + @QtCore.Slot(int) + def _scan_rate_changed(self, index): + val = self._mw.scan_rate.itemData(index) + if val is not None: + self.sigSetScanRate.emit(val.value) @QtCore.Slot() - def _start_stop_pressed(self) -> None: + def _start_stop_pressed(self): if self._mw.start_stop_button.text() == 'Start Measurement': - self.sigStartMeasurement.emit() # Tell the logic to start the scan - - # TODO: This should be a signal + self.sigStartMeasurement.emit() self._terascan_logic().clear_data() - start_wl = self._terascan_logic().start_wavelength - stop_wl = self._terascan_logic().stop_wavelength - self._mw.plot_widget.setXRange(start_wl, stop_wl) else: - self.sigStopMeasurement.emit() # Tell the logic to stop the scan - - - - - # @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)) + self.sigStopMeasurement.emit() - # Private internal functions: - def _scan_started(self) -> None: - """ Update the GUI when the scan starts """ + # ─────────── logic → GUI slots ─────────────────────────────────── + def _scan_started(self): self._mw.start_stop_button.setText('Stop Measurement') self._mw._statusbar.clearMessage() self._mw._progress_bar.setValue(0) - def _scan_stopped(self) -> None: - """ Update the GUI when the scan stops """ + def _scan_stopped(self): self._mw.start_stop_button.setText('Start Measurement') self._mw._statusbar.showMessage('Ready') self._mw._progress_bar.setValue(100) + @QtCore.Slot(int) + def _set_scan_type(self, scan_type): + idx = self._mw.scan_type.findData(scan_type) + if idx >= 0: + self._mw.scan_type.blockSignals(True) + self._mw.scan_type.setCurrentIndex(idx) + self._mw.scan_type.blockSignals(False) + + @QtCore.Slot(int) + def _set_scan_rate(self, scan_rate): + idx = self._mw.scan_rate.findData(scan_rate) + if idx >= 0: + self._mw.scan_rate.blockSignals(True) + self._mw.scan_rate.setCurrentIndex(idx) + self._mw.scan_rate.blockSignals(False) + + @QtCore.Slot(float, object, object) + def _update_data(self, _timestamp, wavelength_data, counts_data): + """Slot connected to logic.sigNewData""" + self.wavelength_data = list(wavelength_data) + self.counts_data = list(counts_data) + + # ─────────── GUI housekeeping ──────────────────────────────────── @QtCore.Slot() def __update_gui(self): + if not self.wavelength_data: + return + x_array = self.wavelength_data y_array = self.counts_data - - if len(x_array) == 0 or len(y_array) == 0: - return - # 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') - - - self._mw.data_item.setData( - x=x_array, - y=y_array, - ) + window = self._mw.spin_avg_points.value() + if 1 < window <= len(y_array): + y_array = np.convolve(y_array, + np.ones(window) / window, + mode='same') + 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 - - @QtCore.Slot() - def _update_data(self, wavelength_data, counts_data) -> None: - """ Update the data from the logic module """ - self.wavelength_data = wavelength_data - self.counts_data = counts_data + def _update_running_avg_points(self, pts): + self._running_avg_points = pts + # expose the window to Qudi’s tray show-action + def show(self): + self._mw.show() + self._mw.raise_() + def _save_data(self) -> None: + """Forward the File ▸ Save Data action to the logic module.""" + self.sigSaveData.emit() + +# ────────────────────────────────────────────────────────────────────── +# unchanged TerascanMainWindow – kept verbatim +# ( only inside file so import works in-place ) +# ────────────────────────────────────────────────────────────────────── class TerascanMainWindow(QtWidgets.QMainWindow): """ Main window for Terascan measurement """ def __init__(self, *args, **kwargs): @@ -223,7 +250,7 @@ def __init__(self, *args, **kwargs): self.setWindowTitle('Terascan Measurement') self.resize(1250, 500) - # Create menu bar + # menu bar menu_bar = QtWidgets.QMenuBar() menu = menu_bar.addMenu('File') self.action_save_data = QtWidgets.QAction('Save Data') @@ -231,102 +258,88 @@ def __init__(self, *args, **kwargs): self.action_save_data.setIcon(QtGui.QIcon(path)) menu.addAction(self.action_save_data) menu.addSeparator() - - self.action_close = QtWidgets.QAction('Close') - path = os.path.join(get_artwork_dir(), 'icons', 'application-exit') - self.action_close.setIcon(QtGui.QIcon(path)) - self.action_close.triggered.connect(self.close) - menu.addAction(self.action_close) + action_close = QtWidgets.QAction('Close') + action_close.setIcon(QtGui.QIcon(os.path.join(get_artwork_dir(), + 'icons', + 'application-exit'))) + action_close.triggered.connect(self.close) + menu.addAction(action_close) self.setMenuBar(menu_bar) - # Create statusbar and indicators + # status-bar self._statusbar = self.statusBar() 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) + 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 for wavelengths, scan, etc. + # widgets self.start_wavelength_label = QtWidgets.QLabel('Start Wavelength (um)') - self.start_wavelength = QtWidgets.QDoubleSpinBox() - self.start_wavelength.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons) - self.start_wavelength.setAlignment(QtCore.Qt.AlignHCenter) - self.start_wavelength.setRange(0.3, 2) - self.start_wavelength.setDecimals(6) + self.start_wavelength = _spinbox() self.stop_wavelength_label = QtWidgets.QLabel('Stop Wavelength (um)') - self.stop_wavelength = QtWidgets.QDoubleSpinBox() - self.stop_wavelength.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons) - self.stop_wavelength.setAlignment(QtCore.Qt.AlignHCenter) - self.stop_wavelength.setRange(0.3, 2) - self.stop_wavelength.setDecimals(6) - + self.stop_wavelength = _spinbox() + self.scan_rate_label = QtWidgets.QLabel('Scan Rate') self.scan_rate = QtWidgets.QComboBox() - + self.scan_type_label = QtWidgets.QLabel('Scan Type') self.scan_type = QtWidgets.QComboBox() self.plot_widget = pg.PlotWidget() - self.plot_widget.setAntialiasing(False) - self.plot_widget.getPlotItem().setContentsMargins(1, 1, 1, 1) - self.plot_widget.setSizePolicy(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.SolidLine), - # downsampling if you want - # downsample=10, # Render 1 out of every 10 points - # downsampleMethod='mean' # Average points for smoother results - ) + pen=pg.mkPen(palette.c1, style=QtCore.Qt.SolidLine)) self.plot_widget.addItem(self.data_item) - # Running Average controls + # 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 + self.spin_avg_points.setValue(5) - # The Start Measurement button (we want this at the very bottom) self.start_stop_button = QtWidgets.QPushButton('Start Measurement') - - # Arrange widgets in layout + + # layout layout = QtWidgets.QGridLayout() layout.addWidget(self.plot_widget, 0, 0, 4, 4) - - 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.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) + + controls = QtWidgets.QVBoxLayout() + for lab, wid in ( + (self.scan_type_label, self.scan_type), + (self.scan_rate_label, self.scan_rate), + (self.start_wavelength_label, self.start_wavelength), + (self.stop_wavelength_label, self.stop_wavelength), + ): + controls.addWidget(lab, 0, QtCore.Qt.AlignBottom) + controls.addWidget(wid, 0, QtCore.Qt.AlignTop) + + controls.addWidget(self.checkbox_running_avg) + controls.addWidget(self.label_avg_points) + controls.addWidget(self.spin_avg_points) + controls.addStretch() + controls.addWidget(self.start_stop_button) + + layout.addLayout(controls, 0, 5, 5, 1) layout.setColumnStretch(1, 1) - - central_widget = QtWidgets.QWidget() - central_widget.setLayout(layout) - self.setCentralWidget(central_widget) + central = QtWidgets.QWidget() + central.setLayout(layout) + self.setCentralWidget(central) + + +def _spinbox(): + sb = QtWidgets.QDoubleSpinBox() + sb.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons) + sb.setAlignment(QtCore.Qt.AlignHCenter) + sb.setRange(700, 1000) + sb.setDecimals(6) + return sb diff --git a/src/qudi/hardware/laser/solstis_laser.py b/src/qudi/hardware/laser/solstis_laser.py index e130309..1777805 100644 --- a/src/qudi/hardware/laser/solstis_laser.py +++ b/src/qudi/hardware/laser/solstis_laser.py @@ -19,6 +19,7 @@ from PySide2 import QtCore import time +from enum import Enum from qudi.core.configoption import ConfigOption from qudi.core.statusvariable import StatusVar @@ -399,4 +400,38 @@ def __status_update(self): self.log.exception(f'Failure getting status: {e.message}') self.statusvar = -1 timestamp = time.perf_counter() - self.sigNewData.emit(timestamp, self.statusvar) \ No newline at end of file + self.sigNewData.emit(timestamp, self.statusvar) + + + class TeraScanType(Enum): + SCAN_TYPE_MEDIUM = 1 + SCAN_TYPE_FINE = 2 + SCAN_TYPE_LINE = 3 + + class TeraScanRate(Enum): + SCAN_RATE_MEDIUM_100_GHZ = 4 + SCAN_RATE_MEDIUM_50_GHZ = 5 + SCAN_RATE_MEDIUM_20_GHZ = 6 + SCAN_RATE_MEDIUM_15_GHZ = 7 + SCAN_RATE_MEDIUM_10_GHZ = 8 + SCAN_RATE_MEDIUM_5_GHZ = 9 + SCAN_RATE_MEDIUM_2_GHZ = 10 + SCAN_RATE_MEDIUM_1_GHZ = 11 + SCAN_RATE_FINE_LINE_20_GHZ = 12 + SCAN_RATE_FINE_LINE_10_GHZ = 13 + SCAN_RATE_FINE_LINE_5_GHZ = 14 + SCAN_RATE_FINE_LINE_2_GHZ = 15 + SCAN_RATE_FINE_LINE_1_GHZ = 16 + SCAN_RATE_FINE_LINE_500_MHZ = 17 + SCAN_RATE_FINE_LINE_200_MHZ = 18 + SCAN_RATE_FINE_LINE_100_MHZ = 19 + SCAN_RATE_FINE_LINE_50_MHZ = 20 + SCAN_RATE_FINE_LINE_20_MHZ = 21 + SCAN_RATE_FINE_LINE_10_MHZ = 22 + SCAN_RATE_FINE_LINE_5_MHZ = 23 + SCAN_RATE_FINE_LINE_2_MHZ = 24 + SCAN_RATE_FINE_LINE_1_MHZ = 25 + SCAN_RATE_LINE_500_KHZ = 26 + SCAN_RATE_LINE_200_KHZ = 27 + SCAN_RATE_LINE_100_KHZ = 28 + SCAN_RATE_LINE_50_KHZ = 29 \ No newline at end of file diff --git a/src/qudi/logic/terascan_logic.py b/src/qudi/logic/terascan_logic.py index d663572..b2cb5e8 100644 --- a/src/qudi/logic/terascan_logic.py +++ b/src/qudi/logic/terascan_logic.py @@ -14,6 +14,42 @@ from qudi.interface.daq_reader_interface import ReaderVal +from enum import Enum + +# TODO: This is a copy of the one in the logic module. We should probably move this to a common place. +class TeraScanType(Enum): + SCAN_TYPE_MEDIUM = 1 + SCAN_TYPE_FINE = 2 + SCAN_TYPE_LINE = 3 + +class TeraScanRate(Enum): + SCAN_RATE_MEDIUM_100_GHZ = 4 + SCAN_RATE_MEDIUM_50_GHZ = 5 + SCAN_RATE_MEDIUM_20_GHZ = 6 + SCAN_RATE_MEDIUM_15_GHZ = 7 + SCAN_RATE_MEDIUM_10_GHZ = 8 + SCAN_RATE_MEDIUM_5_GHZ = 9 + SCAN_RATE_MEDIUM_2_GHZ = 10 + SCAN_RATE_MEDIUM_1_GHZ = 11 + SCAN_RATE_FINE_LINE_20_GHZ = 12 + SCAN_RATE_FINE_LINE_10_GHZ = 13 + SCAN_RATE_FINE_LINE_5_GHZ = 14 + SCAN_RATE_FINE_LINE_2_GHZ = 15 + SCAN_RATE_FINE_LINE_1_GHZ = 16 + SCAN_RATE_FINE_LINE_500_MHZ = 17 + SCAN_RATE_FINE_LINE_200_MHZ = 18 + SCAN_RATE_FINE_LINE_100_MHZ = 19 + SCAN_RATE_FINE_LINE_50_MHZ = 20 + SCAN_RATE_FINE_LINE_20_MHZ = 21 + SCAN_RATE_FINE_LINE_10_MHZ = 22 + SCAN_RATE_FINE_LINE_5_MHZ = 23 + SCAN_RATE_FINE_LINE_2_MHZ = 24 + SCAN_RATE_FINE_LINE_1_MHZ = 25 + SCAN_RATE_LINE_500_KHZ = 26 + SCAN_RATE_LINE_200_KHZ = 27 + SCAN_RATE_LINE_100_KHZ = 28 + SCAN_RATE_LINE_50_KHZ = 29 + # Find a way to append valid data to the lists instead of creating new ones. class TerascanLogic(LogicBase): @@ -56,8 +92,11 @@ class TerascanLogic(LogicBase): sigNewData = QtCore.Signal(float, object, object) # timestamp, wl, counts sigScanStarted = QtCore.Signal() sigScanStopped = QtCore.Signal() - sigSetScanType = QtCore.Signal(int) - sigSetScanRate = QtCore.Signal(int) + sigScanRateChanged = QtCore.Signal(int) + sigScanTypeChanged = QtCore.Signal(int) + sigStartWavelengthChanged = QtCore.Signal(float) + sigStopWavelengthChanged = QtCore.Signal(float) + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -170,6 +209,7 @@ def set_start_wavelength(self, start: float): with self._thread_lock: if self.module_state() == 'idle': self.start_wavelength = start + self.sigStartWavelengthChanged.emit(start) else: self.log.warning( 'Tried to configure while a scan was running.'\ @@ -179,6 +219,7 @@ def set_stop_wavelength(self, stop: float): with self._thread_lock: if self.module_state() == 'idle': self.stop_wavelength = stop + self.sigStopWavelengthChanged.emit(stop) else: self.log.warning( 'Tried to configure while a scan was running.'\ @@ -201,8 +242,18 @@ def set_wavelengths(self, start: float, stop: float): def set_scan_rate(self, scan_rate: int): with self._thread_lock: if self.module_state() == 'idle': + # auto-choose type from the enum name + name = TeraScanRate(scan_rate).name + if name.startswith('SCAN_RATE_MEDIUM'): + self.scan_type = TeraScanType.SCAN_TYPE_MEDIUM.value + elif name.startswith('SCAN_RATE_LINE'): + self.scan_type = TeraScanType.SCAN_TYPE_LINE.value + else: + self.scan_type = TeraScanType.SCAN_TYPE_FINE.value self.scan_rate = scan_rate + self.sigScanTypeChanged.emit(self.scan_type) self.sigScanRateChanged.emit(scan_rate) + else: self.log.warning( 'Tried to configure while a scan was running.'\