From 7ba0e3789855b10382859e23f9d7d9f0b9b49836 Mon Sep 17 00:00:00 2001 From: lange50 Date: Fri, 28 Mar 2025 16:29:28 -0400 Subject: [PATCH] Add time averaged photon counter --- cfg/terascan.cfg | 9 +- .../swabian/photon_counts_time_average_gui.py | 93 +++++++++++++++++++ .../photon_counts_time_average_main_window.py | 69 ++++++++++++++ src/qudi/gui/terascan/terascan_gui.py | 7 +- src/qudi/logic/terascan_logic.py | 1 + 5 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 src/qudi/gui/swabian/photon_counts_time_average_gui.py create mode 100644 src/qudi/gui/swabian/photon_counts_time_average_main_window.py diff --git a/cfg/terascan.cfg b/cfg/terascan.cfg index c842f76..3406a67 100644 --- a/cfg/terascan.cfg +++ b/cfg/terascan.cfg @@ -1,6 +1,6 @@ global: # list of modules to load when starting - startup_modules: [terascan_gui] + startup_modules: [photon_counts_time_average_gui, terascan_gui] # Module server configuration for accessing qudi GUI/logic/hardware modules from remote clients remote_modules_server: @@ -38,6 +38,13 @@ gui: connect: terascan_logic: terascan_logic + photon_counts_time_average_gui: + module.Class: 'swabian.photon_counts_time_average_gui.PhotonCountsTimeAverageGui' + connect: + swabian_timetagger: 'swabian_timetagger' + options: + ring_buffer_length_s: 5 + logic: terascan_logic: module.Class: 'terascan_logic.TerascanLogic' diff --git a/src/qudi/gui/swabian/photon_counts_time_average_gui.py b/src/qudi/gui/swabian/photon_counts_time_average_gui.py new file mode 100644 index 0000000..544a765 --- /dev/null +++ b/src/qudi/gui/swabian/photon_counts_time_average_gui.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- + +__all__ = ['PhotonCountsTimeAverageGui'] + +import numpy as np +import os +from PySide2 import QtCore, QtGui +from typing import List +from time import sleep +from collections import deque + +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.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') + + 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( + 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 + + 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().sigPhotonCounts.disconnect(self._counts_changed) + + self._mw.start_button.clicked.disconnect() + + # disable update timer: + self.__timer.stop() + self.__timer.timeout.disconnect() + self.__timer = None + + self._mw.close() + + def show(self) -> None: + """ Mandatory method to show the 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 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 new file mode 100644 index 0000000..e5a5b89 --- /dev/null +++ b/src/qudi/gui/swabian/photon_counts_time_average_main_window.py @@ -0,0 +1,69 @@ +# -*- 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.py b/src/qudi/gui/terascan/terascan_gui.py index a842227..c938253 100644 --- a/src/qudi/gui/terascan/terascan_gui.py +++ b/src/qudi/gui/terascan/terascan_gui.py @@ -22,7 +22,7 @@ class TerascanGui(GuiBase): """ Terascan Measurement GUI - exaple config for copy-paste: + example config for copy-paste: terascan_gui: module.Class: 'terascan.terascan_gui.TerascanGui' connect: @@ -246,10 +246,15 @@ def _update_ui(self, running: bool) -> None: 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): 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 diff --git a/src/qudi/logic/terascan_logic.py b/src/qudi/logic/terascan_logic.py index b830f42..2cb3efe 100644 --- a/src/qudi/logic/terascan_logic.py +++ b/src/qudi/logic/terascan_logic.py @@ -161,6 +161,7 @@ def start_scan(self): self._current_data = [] self._last_locked = time.time() self.sigSetLaserWavelengths.emit(self._start_wavelength, self._end_wavelength) + self.sigStopCounting.emit() # In case we are counting from somewhere else self.sigStartScan.emit() @QtCore.Slot()