Skip to content

Commit

Permalink
Add new terscan_gui
Browse files Browse the repository at this point in the history
This is not carefully tested.
  • Loading branch information
lange50 committed Apr 28, 2025
1 parent d84945e commit 33eb531
Show file tree
Hide file tree
Showing 4 changed files with 666 additions and 200 deletions.
367 changes: 367 additions & 0 deletions src/qudi/gui/terascan/terascan_gui copy.py
Original file line number Diff line number Diff line change
@@ -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)
Loading

0 comments on commit 33eb531

Please sign in to comment.