From c2dd374bb1f5ddab9a0645dfd1fd31b2399579b6 Mon Sep 17 00:00:00 2001 From: lange50 Date: Wed, 30 Apr 2025 09:59:24 -0400 Subject: [PATCH] Fix photon counts window I changed the photon counts window to work with the new swabian internal logic. I also added a number of counts that updates with the plot. --- .../swabian/photon_counts_time_average_gui.py | 299 ++++++++--- .../photon_counts_time_average_main_window.py | 69 --- src/qudi/gui/terascan/terascan_gui copy.py | 367 ------------- .../hardware/timetagger/swabian_tagger.py | 10 +- src/qudi/logic/terascan_logic copy.py | 499 ------------------ src/qudi/logic/terascan_logic.py | 8 +- 6 files changed, 234 insertions(+), 1018 deletions(-) delete mode 100644 src/qudi/gui/swabian/photon_counts_time_average_main_window.py delete mode 100644 src/qudi/gui/terascan/terascan_gui copy.py delete mode 100644 src/qudi/logic/terascan_logic copy.py diff --git a/src/qudi/gui/swabian/photon_counts_time_average_gui.py b/src/qudi/gui/swabian/photon_counts_time_average_gui.py index 22a68a1..0432500 100644 --- a/src/qudi/gui/swabian/photon_counts_time_average_gui.py +++ b/src/qudi/gui/swabian/photon_counts_time_average_gui.py @@ -1,93 +1,242 @@ # -*- coding: utf-8 -*- +"""Photon Counts Time‑Average GUI for Qudi -__all__ = ['PhotonCountsTimeAverageGui'] +This GUI displays live photon‑count data coming from a *FastCounterInterface* logic +module (e.g. a Swabian Time Tagger). It shows a continuously‑updated plot of the +most recent samples, a large read‑out of the mean count‑rate over a configurable +window, and provides optional on‑the‑fly **running averaging** (smoothing) and +**down‑sampling** to reduce plot load. + +Key features +------------ +* **Start button removed** – acquisition starts automatically on activation. +* **Large numerical display** – configurable window (default 250 ms). +* **Running average** – box‑car smoothing over *N* points (user‑settable). +* **Down‑sample** – decimate by averaging groups of *N* points (user‑settable). +* **All user parameters** exposed either as *ConfigOption* (persisted in cfg) + or *StatusVar* (runtime‑modifiable). + +The file contains two classes: +* :class:`PhotonCountsTimeAverageGui` – the Qudi *GuiBase* module. +* :class:`PhotonCountsTimeAverageMainWindow` – the Qt main‑window widget. +""" -import numpy as np -import os -from PySide2 import QtCore, QtGui -from typing import List -from time import sleep from collections import deque +from typing import Deque, List + +import numpy as np +from PySide2 import QtCore, QtGui, QtWidgets +import pyqtgraph as pg -from qudi.util.datastorage import TextDataStorage -from qudi.core.module import GuiBase from qudi.core.connector import Connector -from qudi.core.statusvariable import StatusVar from qudi.core.configoption import ConfigOption -from qudi.gui.swabian.photon_counts_time_average_main_window import PhotonCountsTimeAverageMainWindow -from qudi.util.paths import get_artwork_dir +from qudi.core.module import GuiBase +from qudi.core.statusvariable import StatusVar +from qudi.util.colordefs import QudiPalettePale as palette + +__all__ = ["PhotonCountsTimeAverageGui"] -from qudi.logic.terascan_logic import TerascanData -# TODO: put the maxlen in the config file class PhotonCountsTimeAverageGui(GuiBase): - """ Photon Counting Time Average GUI - - example config for copy-paste: - photon_counts_time_average_gui: - module.Class: 'swabian.photon_counts_time_average_gui.PhotonCountsTimeAverageGui' - connect: - swabian_timetagger: 'swabian_timetagger' - options: - ring_buffer_length_s: 10 - """ - # One logic module - _ring_buffer_length_s = ConfigOption(name='ring_buffer_length_s', default=10, missing='warn') - _photon_counts_logic = Connector(name='swabian_timetagger', interface='FastCounterInterface') - + """Qudi GUI module that visualises live photon‑count data.""" + + # ----------------------------- configuration --------------------------------- + _ring_buffer_length_s: int = ConfigOption( + name="ring_buffer_length_s", default=10, missing="warn", + ) + _average_display_window_ms: int = ConfigOption( + name="average_display_window_ms", default=1000, missing="warn", + ) + _update_interval_ms: int = ConfigOption( + name="update_interval_ms", default=100, missing="warn", + ) + + # ------------------------------ connectors ----------------------------------- + _counter_logic = Connector(name="timetagger", interface="FastCounterInterface") + + # ----------------------------- status‑vars ----------------------------------- + smoothing_window = StatusVar(default=1) + downsample_factor = StatusVar(default=1) + + # ----------------------------------------------------------------------------- + # Qudi lifecycle + # ----------------------------------------------------------------------------- def on_activate(self) -> None: - self._data = deque(maxlen=1000 * self._ring_buffer_length_s) - - # initialize the main window - self._mw = PhotonCountsTimeAverageMainWindow() - - # Signals from GUI: - self._mw.start_button.clicked.connect(self._photon_counts_logic().start_measure) - - # Connect signals from logic modules - # The first function is a QtCore.Slot - self._photon_counts_logic().sigScanFinished.connect( + """Initialise GUI and begin acquisition.""" + # Circular buffer for the most recent raw samples (1 kHz rate) + self._data: Deque[int] = deque(maxlen=1000 * self._ring_buffer_length_s) + + # Build the main window + self._mw = PhotonCountsTimeAverageMainWindow( + avg_window_ms=self._average_display_window_ms + ) + + # Connect GUI → status vars + self._mw.running_avg_checkbox.toggled.connect(self._toggle_running_avg) + self._mw.running_avg_spin.valueChanged.connect(self._change_smoothing) + self._mw.downsample_checkbox.toggled.connect(self._toggle_downsample) + self._mw.downsample_spin.valueChanged.connect(self._change_downsample) + + # Connect counter logic → GUI + self._counter_logic().sigNewData.connect( self._counts_changed, QtCore.Qt.QueuedConnection ) - - # Turn on update timer: - self.__timer = QtCore.QTimer() - self.__timer.setSingleShot(False) # False means that the timer will repeat - self.__timer.timeout.connect(self.__update_plot) - self.__timer.start(250) # 250 ms - + + # Acquisition starts immediately (no separate *Start* button) + try: + self._counter_logic().start_reading() + except AttributeError: + self.log.warning("Connected logic module has no 'start_reading()' method.") + + # Periodic UI refresh + self._timer = QtCore.QTimer(self) + self._timer.setInterval(self._update_interval_ms) + self._timer.timeout.connect(self._refresh_ui) + self._timer.start() + self.show() - - + + # ------------------------------------------------------------------------- def on_deactivate(self) -> None: - # When you call a connector, you should do it as a function, as shown here. Noone knows why. - # For some reason, when connecting to an external connector, you also need to specify which function you are disconnecting - self._photon_counts_logic().sigScanFinished.disconnect(self._counts_changed) - - self._mw.start_button.clicked.disconnect() - - # disable update timer: - self.__timer.stop() - self.__timer.timeout.disconnect() - self.__timer = None - + """Clean‑up connections and stop timers.""" + self._counter_logic().sigNewData.disconnect(self._counts_changed) + if self._timer is not None: + self._timer.stop() + self._timer.timeout.disconnect() + self._timer = None self._mw.close() - - def show(self) -> None: - """ Mandatory method to show the main window """ + + # ------------------------------------------------------------------------- + def show(self) -> None: # noqa: D401 (Qudi naming convention) + """Show (raise) the Qt main‑window.""" self._mw.show() self._mw.raise_() - - @QtCore.Slot(np.ndarray) - def _counts_changed(self, counts: np.ndarray) -> None: - self._data.append(np.sum(counts)) - - def __update_plot(self) -> None: - if (len(self._data) == 0): - return - - x = range(len(self._data)) - y = list(self._data) - - - self._mw.data_item.setData(x = x, y = y) \ No newline at end of file + + # ------------------------------------------------------------------------- + # Slots + # ------------------------------------------------------------------------- + @QtCore.Slot(float, np.ndarray) + def _counts_changed(self, timestamp: float, counts: np.ndarray) -> None: + """Receive new data burst (1 ms bins) from the logic module.""" + self._data.extend(counts.astype(int)) + + # ------------------------------------------------------------------------- + def _refresh_ui(self) -> None: + """Update numeric display and plot.""" + if not self._data: + return # nothing to show yet + + # ------------------------- numeric display --------------------------- + window_pts = min(len(self._data), self._average_display_window_ms) + recent = list(self._data)[-window_pts:] + cps = np.mean(recent) * 1000.0 # convert to counts/s (Hz) + self._mw.avg_label.setText(f"{cps:,.0f} cps") + + # ----------------------------- plotting ------------------------------ + y = np.fromiter(self._data, dtype=np.int64) # fast + avoids copy + + # Running average (box‑car smoothing) + if self.smoothing_window > 1 and self._mw.running_avg_checkbox.isChecked(): + kernel = np.ones(self.smoothing_window) / self.smoothing_window + y = np.convolve(y, kernel, mode="valid") + x_offset = self.smoothing_window - 1 + else: + x_offset = 0 + + # Down‑sample (decimate by averaging groups) + if self.downsample_factor > 1 and self._mw.downsample_checkbox.isChecked(): + excess = y.size % self.downsample_factor + if excess: + y = y[:-excess] # truncate so that len is divisible by factor + y = y.reshape(-1, self.downsample_factor).mean(axis=1) + x_vals = np.arange(y.size) * self.downsample_factor + x_offset + else: + x_vals = np.arange(y.size) + x_offset + + self._mw.data_item.setData(x=x_vals, y=y) + + # ------------------------------------------------------------------------- + # GUI → Status‑Var handlers + # ------------------------------------------------------------------------- + def _toggle_running_avg(self, checked: bool) -> None: + self._mw.running_avg_spin.setEnabled(checked) + + def _change_smoothing(self, value: int) -> None: + self.smoothing_window = max(1, value) + + def _toggle_downsample(self, checked: bool) -> None: + self._mw.downsample_spin.setEnabled(checked) + + def _change_downsample(self, value: int) -> None: + self.downsample_factor = max(1, value) + + +# ============================================================================== +# Main Window +# ============================================================================== +class PhotonCountsTimeAverageMainWindow(QtWidgets.QMainWindow): + """Qt window that hosts the plot, large count‑rate display and controls.""" + + def __init__(self, *, avg_window_ms: int, parent: QtWidgets.QWidget | None = None): + super().__init__(parent) + self.setWindowTitle("Time‑Averaged Photon Counts") + + # ----------------------------- widgets -------------------------------- + # Large numeric display (readable across the lab) + self.avg_label = QtWidgets.QLabel("0 cps") + big_font = QtGui.QFont() + big_font.setPointSize(48) + big_font.setBold(True) + self.avg_label.setFont(big_font) + self.avg_label.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter) + + # Live plot + self.plot_widget = pg.PlotWidget() + self.plot_widget.getPlotItem().setContentsMargins(1, 1, 1, 1) + self.plot_widget.setLabel("bottom", "Sample # (ms)") + self.plot_widget.setLabel("left", "Counts / ms") + + self.data_item = pg.PlotDataItem( + pen=pg.mkPen(palette.c1), symbol="o", symbolPen=palette.c1, + symbolBrush=palette.c1, symbolSize=5 + ) + self.plot_widget.addItem(self.data_item) + + # ------------------------ averaging controls ------------------------- + # Running average + self.running_avg_checkbox = QtWidgets.QCheckBox("Running average") + self.running_avg_spin = QtWidgets.QSpinBox() + self.running_avg_spin.setRange(1, 5000) + self.running_avg_spin.setValue(1) + self.running_avg_spin.setEnabled(False) + self.running_avg_checkbox.setToolTip("Smooth data by averaging over N points") + + # Down‑sample + self.downsample_checkbox = QtWidgets.QCheckBox("Down‑sample") + self.downsample_spin = QtWidgets.QSpinBox() + self.downsample_spin.setRange(1, 5000) + self.downsample_spin.setValue(1) + self.downsample_spin.setEnabled(False) + self.downsample_checkbox.setToolTip("Average N points and plot only that average") + + # ----------------------------- layout --------------------------------- + controls_layout = QtWidgets.QGridLayout() + controls_layout.addWidget(self.running_avg_checkbox, 0, 0) + controls_layout.addWidget(self.running_avg_spin, 0, 1) + controls_layout.addWidget(self.downsample_checkbox, 1, 0) + controls_layout.addWidget(self.downsample_spin, 1, 1) + controls_group = QtWidgets.QGroupBox("Data reduction") + controls_group.setLayout(controls_layout) + + central_layout = QtWidgets.QVBoxLayout() + central_layout.addWidget(self.avg_label) + central_layout.addWidget(self.plot_widget, 1) + central_layout.addWidget(controls_group) + + central_widget = QtWidgets.QWidget() + central_widget.setLayout(central_layout) + self.setCentralWidget(central_widget) + + # ---------------------------- misc tweaks ---------------------------- + self.resize(900, 600) + self.show() diff --git a/src/qudi/gui/swabian/photon_counts_time_average_main_window.py b/src/qudi/gui/swabian/photon_counts_time_average_main_window.py deleted file mode 100644 index e5a5b89..0000000 --- a/src/qudi/gui/swabian/photon_counts_time_average_main_window.py +++ /dev/null @@ -1,69 +0,0 @@ -# -*- coding: utf-8 -*- - -__all__ = ['PhotonCountsTimeAverageMainWindow'] - -import os # imported for potential future use (e.g., loading icons or other assets) -from PySide2 import QtGui, QtCore, QtWidgets -import pyqtgraph as pg - -# Although these imports are not used in the current code, they might be needed for extended functionality. -from qudi.util.widgets.plotting.image_widget import ImageWidget -from qudi.util.paths import get_artwork_dir -from qudi.util.colordefs import QudiPalettePale as palette -from qudi.hardware.laser.solstis_constants import * # wildcard import used per Qudi convention - -class PhotonCountsTimeAverageMainWindow(QtWidgets.QMainWindow): - """ - Main window for displaying time-averaged photon counts. - - This window contains a live-updating plot using pyqtgraph that displays photon counts on the y-axis - versus time (in seconds) on the x-axis. The current implementation sets up the plot layout and visual style. - Additional widgets, such as an LCD display for the live number, are provided as commented code for future use. - """ - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - # Set the window title to reflect its function - self.setWindowTitle('Time Averaged Photon Counts') - - # Create the plot widget using pyqtgraph - self.plot_widget = pg.PlotWidget() - # Set minimal margins for the plot area - self.plot_widget.getPlotItem().setContentsMargins(1, 1, 1, 1) - # Ensure the widget expands with the window - self.plot_widget.setSizePolicy(QtWidgets.QSizePolicy.Expanding, - QtWidgets.QSizePolicy.Expanding) - # Prevent the plot widget from receiving focus - self.plot_widget.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) - # Set axis labels - self.plot_widget.setLabel('bottom', text='Time', units='s') - self.plot_widget.setLabel('left', text='Counts') - - # Create a PlotDataItem for displaying data points on the plot - 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) - - self.start_button = QtWidgets.QPushButton('Start') - - # Optional: An LCD widget to display the current photon count as a number. - # Uncomment and adjust if a numerical display is required. - # self.lcd = QtWidgets.QLCDNumber() - # self.lcd.setDigitCount(5) - - # Arrange widgets in a grid layout - layout = QtWidgets.QGridLayout() - # Place the plot widget spanning 4 rows and 4 columns - layout.addWidget(self.plot_widget, 0, 0, 4, 4) - layout.addWidget(self.start_button, 4, 5, 1, 1) - # Set column stretch to ensure proper scaling - layout.setColumnStretch(1, 1) - - # Create a central widget, set the layout, and then assign it as the main window's central widget - central_widget = QtWidgets.QWidget() - central_widget.setLayout(layout) - self.setCentralWidget(central_widget) diff --git a/src/qudi/gui/terascan/terascan_gui copy.py b/src/qudi/gui/terascan/terascan_gui copy.py deleted file mode 100644 index e912a8c..0000000 --- a/src/qudi/gui/terascan/terascan_gui copy.py +++ /dev/null @@ -1,367 +0,0 @@ -# -*- 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/hardware/timetagger/swabian_tagger.py b/src/qudi/hardware/timetagger/swabian_tagger.py index 9cbd750..2648eb4 100644 --- a/src/qudi/hardware/timetagger/swabian_tagger.py +++ b/src/qudi/hardware/timetagger/swabian_tagger.py @@ -66,9 +66,10 @@ def on_activate(self): # TODO: Should be a slot i think def start_reading(self): - self.module_state.lock() - self._counter.start() - self.statusvar = 2 + if self.module_state() == 'idle': + self.module_state.lock() + self._counter.start() + self.statusvar = 2 # TODO: Should be a slot @@ -114,5 +115,6 @@ def __update_data(self): if self.module_state() == 'locked': data_obj = self._counter.getDataObject(remove=True) timestamp = time.perf_counter() - self.most_recent_data = np.squeeze(data_obj.getData()) + # self.most_recent_data = np.squeeze(data_obj.getData()) + self.most_recent_data = np.squeeze(data_obj.getDataNormalized()) / 1000 # convert to ms^-1 self.sigNewData.emit(timestamp, self.most_recent_data) diff --git a/src/qudi/logic/terascan_logic copy.py b/src/qudi/logic/terascan_logic copy.py deleted file mode 100644 index d663572..0000000 --- a/src/qudi/logic/terascan_logic copy.py +++ /dev/null @@ -1,499 +0,0 @@ - -import numpy as np -import time -from PySide2 import QtCore - -from typing import List, Dict - -from qudi.core.module import LogicBase -from qudi.util.mutex import RecursiveMutex -from qudi.core.connector import Connector -from qudi.core.configoption import ConfigOption -from qudi.core.statusvariable import StatusVar -from qudi.util.datastorage import TextDataStorage - -from qudi.interface.daq_reader_interface import ReaderVal - - -# Find a way to append valid data to the lists instead of creating new ones. -class TerascanLogic(LogicBase): - """ - This is the Logic class for Terascan measurements - - example config for copy-paste: - - terascan_logic: - module.Class: 'terascan_logic.TerascanLogic' - connect: - laser: solstis_laser - wavemeter: wavemeter - counter: swabian_timetagger - daq: nidaq # Note that this logic assumes there is exactly one (digital) input to the DAQ. - options: - record_length_ms: 1 # Length of data (in ms) to take at each wavelength. Only integers. - laser_timeout_s: 10 # Time in seconds to wait for the laser to lock before automatically restarting the scan - mode_hop_overlap_med: 0.001 # in nm, from the SolsTiS control panel. This is how far back we go to discard data every time a mode hop occurs - mode_hop_overlap_fine: 0.00025 # "" - """ - - #################### CONNECTORS #################### - _laser = Connector(name='laser', interface='Base') - _wavemeter = Connector(name='wavemeter', interface='Base') - _counter = Connector(name='counter', interface='Base') - _daq = Connector(name='daq', interface='Base') - - #################### CONFIGURATION OPTIONS #################### - save_dir = ConfigOption('save_dir', default='C:\\Users\\hoodl\\qudi\\Data', missing='warn') - _laser_timeout_s = ConfigOption(name='laser_timeout_s', default=10) - - #################### STATUS VARIABLES #################### - start_wavelength = StatusVar('start_wavelength', default=0.785) - stop_wavelength = StatusVar('stop_wavelength', default=0.7851) - scan_rate = StatusVar('scan_rate', default=12) # SCAN_RATE_FINE_LINE_20_GHZ - scan_type = StatusVar('scan_type', default=2) # SCAN_TYPE_FINE - - #################### SIGNALS FOR OTHER LOGIC MODULES #################### - sigNewData = QtCore.Signal(float, object, object) # timestamp, wl, counts - sigScanStarted = QtCore.Signal() - sigScanStopped = QtCore.Signal() - sigSetScanType = QtCore.Signal(int) - sigSetScanRate = QtCore.Signal(int) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.__timer = None - self._thread_lock = RecursiveMutex() - - ##################### EXPERIMENT PARAMETERS #################### - - ############## DATA #################### - self._time_since_last_wl_change = time.perf_counter() - self._curr_wavelength = 0.0 - self._forward_direction = True - - """ - All of these lists should eventually be the same length, except for laser_status_data. - That is just for bookkeeping purposes. - """ - self.laser_status_data = [] # List of laser status data (0 = idle, 1 = scanning, 2 = error) - self.counts_data = [] - self.wavelength_data = [] - self.ttl_data = [] - # These all have a common length and are calculated using ttl_data - self.valid_counts_data = [] - self.valid_wavelength_data = [] - - self.set_scan_type(self.scan_type) - self.set_scan_rate(self.scan_rate) - - def on_activate(self): - # TODO: Is it a good idea to do this here? Or should I always do self._instrument()? - laser = self._laser() - counter = self._counter() - wavemeter = self._wavemeter() - daq = self._daq() - - ##################### INITIALIZE DATA INPUT #################### - counter.start_reading() - wavemeter.start_reading() - daq.start_reading() - - #################### OUTPUTS #################### - # self.sigStartScan.connect(laser.start_scan, QtCore.Qt.QueuedConnection) - # self.sigStopScan.connect(laser.stop_scan, QtCore.Qt.QueuedConnection) - # self.sigSetWavelengths.connect(laser.set_wavelengths, QtCore.Qt.QueuedConnection) - # self.sigSetScanRate.connect(laser.set_scan_rate, QtCore.Qt.QueuedConnection) - # self.sigSetScanType.connect(laser.set_scan_type, QtCore.Qt.QueuedConnection) - - #################### INPUTS #################### - laser.sigNewData.connect(self._new_laser_data) - wavemeter.sigNewData.connect(self._new_wavemeter_data) - counter.sigNewData.connect(self._new_counter_data) - daq.sigNewData.connect(self._new_daq_data) - - #################### WATCHDOG TIMER #################### - self.__timer = QtCore.QTimer() - self.__timer.setSingleShot(False) - self.__timer.timeout.connect(self.__update_scan) - self.__timer.timeout.connect(self.__update_data) - self.__timer.start(100) # 100 ms timer - - def on_deactivate(self): - self.__timer.stop() - self.__timer.timeout.disconnect() - self.__timer = None - - @property - def scan_types(self) -> dict: - return self._laser().get_scan_types - - @property - def scan_rates(self) -> dict: - return self._laser().get_scan_rates - - def get_start_wavelength(self) -> float: - with self._thread_lock: - return self.start_wavelength - - def get_stop_wavelength(self) -> float: - with self._thread_lock: - return self.stop_wavelength - - def get_scan_rate(self) -> int: - with self._thread_lock: - return self.scan_rate - - def get_scan_type(self) -> int: - with self._thread_lock: - return self.scan_type - - def start_scan(self): - with self._thread_lock: - if self.module_state() == 'idle': - self.module_state.lock() - self.sigScanStarted.emit() - - self._laser().set_wavelengths(self.start_wavelength, self.stop_wavelength) - self._laser().set_scan_type(self.scan_type) - self._laser().set_scan_rate(self.scan_rate) - self._laser().start_scan() - - def stop_scan(self): - with self._thread_lock: - if self.module_state() == 'locked': - self.module_state.unlock() - self.sigScanStopped.emit() - self._laser().stop_scan() - - - def set_start_wavelength(self, start: float): - with self._thread_lock: - if self.module_state() == 'idle': - self.start_wavelength = start - else: - self.log.warning( - 'Tried to configure while a scan was running.'\ - 'Please wait until it is finished or stop it.') - - def set_stop_wavelength(self, stop: float): - with self._thread_lock: - if self.module_state() == 'idle': - self.stop_wavelength = stop - else: - self.log.warning( - 'Tried to configure while a scan was running.'\ - 'Please wait until it is finished or stop it.') - - # TODO: Deprecated. Use individual set_start_wavelength and set_stop_wavelength instead. - @QtCore.Slot(float, float) - def set_wavelengths(self, start: float, stop: float): - with self._thread_lock: - if self.module_state() == 'idle': - self.start_wavelength = start - self.stop_wavelength = stop - self._forward_direction = start < stop - else: - self.log.warning( - 'Tried to configure while a scan was running.'\ - 'Please wait until it is finished or stop it.') - - @QtCore.Slot(int) - def set_scan_rate(self, scan_rate: int): - with self._thread_lock: - if self.module_state() == 'idle': - self.scan_rate = scan_rate - self.sigScanRateChanged.emit(scan_rate) - else: - self.log.warning( - 'Tried to configure while a scan was running.'\ - 'Please wait until it is finished or stop it.') - - @QtCore.Slot(int) - # TODO: Should this also set the solstis scan type? Or is that done in the start_scan method? - def set_scan_type(self, scan_type: int): - with self._thread_lock: - if self.module_state() == 'idle': - self.scan_type = scan_type - self.sigScanTypeChanged.emit(scan_type) - else: - self.log.warning( - 'Tried to configure while a scan was running.'\ - 'Please wait until it is finished or stop it.') - - - @QtCore.Slot(float, np.ndarray) - def _new_counter_data(self, timestamp, data: np.ndarray): - with self._thread_lock: - if self.module_state() == 'locked': - new_counts_data = data.tolist() - self.counts_data.extend(new_counts_data) - - - @QtCore.Slot(float, object) - def _new_wavemeter_data(self, timestamp, data: np.float64): - """Called on every new wavemeter reading when locked.""" - with self._thread_lock: - # only run when we’re in the ‘locked’ state - if self.module_state() != 'locked': - return - - most_recent_wavelength = self.wavelength_data[-1] if len(self.wavelength_data) > 0 else data - n_new_points = len(self.counts_data) - len(self.wavelength_data) - new_wavelength_data = np.linspace(most_recent_wavelength, data, n_new_points).tolist() - self.wavelength_data.extend(new_wavelength_data) - - - @QtCore.Slot(float, object) - def _new_daq_data(self, timestamp, data: bool): - with self._thread_lock: - if self.module_state() != 'locked': - return - - n_new_points = len(self.counts_data) - len(self.ttl_data) - new_ttl_data = [data] * n_new_points - self.ttl_data.extend(new_ttl_data) - - - @QtCore.Slot(float, object) - def _new_laser_data(self, timestamp, data: ReaderVal): - with self._thread_lock: - if self.module_state() != 'locked': - return - self.laser_status_data += [data] - - - def clear_data(self): - with self._thread_lock: - self.laser_status_data = [] - self.wavelength_data = [] - self.counts_data = [] - self.ttl_data = [] - self.valid_counts_data = [] - self.valid_wavelength_data = [] - self._curr_wavelength = 0.0 - self._time_since_last_wl_change = time.perf_counter() - - - # TODO: Clean this up. I don't like the logic. - @QtCore.Slot() - def save_data(self) -> None: - ds = TextDataStorage( - root_dir=self.save_dir, # Use the configurable save directory - column_formats='.15e' - ) - array = np.column_stack(( - self.valid_wavelength_data, - self.valid_counts_data, - self.ttl_data, - self.wavelength_data, - self.counts_data, - )) - ds.save_data(array) - - - def __update_data(self): - with self._thread_lock: - if self.module_state() != 'locked': - return - - # TODO: Make this more efficient. It's not a good idea to recreate the valid data lists every time. - ######################### UPDATE VALID DATA ######################### - common_length = min(len(self.counts_data), len(self.wavelength_data), len(self.ttl_data)) - mask = self.ttl_data[:common_length] - self.valid_counts_data = np.array(self.counts_data[:common_length])[mask].tolist() - self.valid_wavelength_data = np.array(self.wavelength_data[:common_length])[mask].tolist() - - # update valid counts - # counts_mask = self.ttl_data[len(self.valid_counts_data):new_common_length] - # new_counts = np.array(self.counts_data[len(self.valid_counts_data):new_common_length])[counts_mask].tolist() - # self.valid_counts_data.extend(new_counts) - - # # update valid wavelength - # wavelength_mask = self.ttl_data[len(self.valid_wavelength_data):new_common_length] - # new_wavelength = np.array(self.wavelength_data[len(self.valid_wavelength_data):new_common_length])[wavelength_mask].tolist() - # self.valid_wavelength_data.extend(new_wavelength) - - # Emit the new data signal - self.sigNewData.emit(time.perf_counter(), self.valid_wavelength_data, self.valid_counts_data) - - - def __update_scan(self): - with self._thread_lock: - if self.module_state() != 'locked': - self.log.debug("Watchdog: module not locked, skipping") - return - - if not self.laser_status_data: - self.log.debug("Watchdog: no laser status yet") - return - - last_status = self.laser_status_data[-1] - self.log.debug(f"Watchdog: last laser status = {last_status}") - - # 1) If laser is actively scanning, nothing to do - if last_status == 1: - self.log.debug("Watchdog: laser is scanning → ok") - return - - # 2) If laser idle, check for normal scan end - if last_status == 0: - if self._check_for_end_of_scan(): - self.log.info("Watchdog: reached end wavelength, stopping scan") - self.stop_scan() - return - else: - self.log.debug("Watchdog: laser idle but not at end yet") - - # 3) Detect stalled motion by wavemeter - if self._update_wavelength_motion(): - self.log.debug(f"Watchdog: motion detected, updated curr_wavelength to {self._curr_wavelength}") - return # reset timer, leave scan running - - # 4) Timeout: no motion for > timeout_s - if time.perf_counter() - self._time_since_last_wl_change > self._laser_timeout_s: - self.log.warning("Watchdog: motion timeout exceeded, restarting scan") - self._restart_from_last_valid() - - # ——— Helpers —————————————————————————————————————————————— - def _check_for_end_of_scan(self) -> bool: - """ - In order for the scan to be considered finished, - 1. There must be a valid wavelength (wl with ttl high) that is close to the start wavelength - 2. The last valid wavelength must be close to the stop wavelength - """ - - # First check - tol = 1e-4 - if not self.wavelength_data: - self.log.debug("Motion check: no wavelength data yet") - return False - n = min(len(self.wavelength_data), len(self.ttl_data), len(self.counts_data)) - mask = self.ttl_data[:n] - valid_wl = np.asarray(self.wavelength_data[:n])[mask] - - if np.any(np.isclose(valid_wl, self.start_wavelength, atol=tol)) and \ - np.any(np.isclose(valid_wl, self.stop_wavelength, atol=tol)): - self.log.debug("Check end-of-scan: found valid wavelength close to start and stop") - return True - else: - return False - - - def _update_wavelength_motion(self) -> bool: - """ - Check if the wavemeter has recorded a new wavelength since last seen. - If so, update curr_wavelength & reset timeout timer. - Returns True if motion detected. - """ - if not self.wavelength_data: - self.log.debug("Motion check: no wavelength data yet") - return False - - latest = self.wavelength_data[-1] - delta = abs(self._curr_wavelength - latest) - threshold = 2e-5 - - if delta > threshold: - self.log.info(f"Motion detected: Δλ={delta:.2e} nm > {threshold:.2e}") - self._curr_wavelength = latest - self._time_since_last_wl_change = time.perf_counter() - return True - - self.log.debug(f"No motion: Δλ={delta:.2e} nm ≤ threshold {threshold:.2e}") - return False - - def _restart_from_last_valid(self): - """ - Find the last TTL‐valid wavelength, stop the scan, and restart - from there to the end_wavelength. - """ - n = min(len(self.wavelength_data), len(self.ttl_data), len(self.counts_data)) - mask = self.ttl_data[:n] - valid = [wl for wl, ok in zip(self.wavelength_data[:n], mask) if ok] - - if valid: - last_wl = valid[-1] - else: - last_wl = self.start_wavelength - self.log.warning("No valid TTL points found → restarting from start_wavelength") - - self.log.info(f"Restarting scan from λ={last_wl} to {self.stop_wavelength}") - self._laser().stop_scan() - time.sleep(1) - self._laser().set_wavelengths(last_wl, self.stop_wavelength) - self._laser().start_scan() - - - - -class TerascanLogicData: - def __init__(self): - """Initialize the TerascanLogicData class with empty data lists. - - The valid data lists are equal in length and calculated using ttl_data. - """ - self.counts_data = [] - self.wavelength_data = [] - self.ttl_data = [] - - self.valid_counts_data = [] - self.valid_wavelength_data = [] - - def get_counts_data(self) -> List[float]: - """Get the counts data. - - Returns: - list: The counts data. - """ - return self.counts_data - - def get_wavelength_data(self) -> List[float]: - """Get the wavelength data. - - Returns: - list: The wavelength data. - """ - return self.wavelength_data - - def get_ttl_data(self) -> List[bool]: - """Get the TTL data. - - Returns: - list: The TTL data. - """ - return self.ttl_data - - def add_counts_data(self, counts_data: List[float]): - """Add counts data to the TerascanLogicData object. - - Args: - counts_data (list): A list of counts data to be added. - """ - self.counts_data.extend(counts_data) - - def add_wavelength_data(self, wavelength_data: List[float]): - """Add wavelength data to the TerascanLogicData object. - - Args: - wavelength_data (list): A list of wavelength data to be added. - """ - self.wavelength_data.extend(wavelength_data) - - def add_ttl_data(self, ttl_data: List[bool]): - """Add TTL data to the TerascanLogicData object. - - Args: - ttl_data (list): A list of TTL data to be added. - """ - self.ttl_data.extend(ttl_data) - new_common_length = min(len(self.counts_data), len(self.wavelength_data), len(self.ttl_data)) - - # update valid counts - counts_mask = self.ttl_data[len(self.valid_counts_data):new_common_length] - new_counts = np.array(self.counts_data[len(self.valid_counts_data):new_common_length])[counts_mask].tolist() - self.valid_counts_data.extend(new_counts) - - # update valid wavelength - wavelength_mask = self.ttl_data[len(self.valid_wavelength_data):new_common_length] - new_wavelength = np.array(self.wavelength_data[len(self.valid_wavelength_data):new_common_length])[wavelength_mask].tolist() - self.valid_wavelength_data.extend(new_wavelength) \ No newline at end of file diff --git a/src/qudi/logic/terascan_logic.py b/src/qudi/logic/terascan_logic.py index 9a32fa3..646fe0e 100644 --- a/src/qudi/logic/terascan_logic.py +++ b/src/qudi/logic/terascan_logic.py @@ -245,13 +245,13 @@ 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 + name = self._laser().TeraScanRate(scan_rate).name if name.startswith('SCAN_RATE_MEDIUM'): - self.scan_type = TeraScanType.SCAN_TYPE_MEDIUM.value + self.scan_type = self._laser().TeraScanType.SCAN_TYPE_MEDIUM.value elif name.startswith('SCAN_RATE_LINE'): - self.scan_type = TeraScanType.SCAN_TYPE_LINE.value + self.scan_type = self._laser().TeraScanType.SCAN_TYPE_LINE.value else: - self.scan_type = TeraScanType.SCAN_TYPE_FINE.value + self.scan_type = self._laser().TeraScanType.SCAN_TYPE_FINE.value self.scan_rate = scan_rate self.sigScanTypeChanged.emit(self.scan_type) self.sigScanRateChanged.emit(scan_rate)